diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..0ba166ff6 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,19 @@ +[run] +branch = True +source = + config + general + locale + resources + static + templates + topics + utils +omit = + # Omit migration files + */migrations/* + # Omit database proxy file used with Google Cloud SQL Proxy + csunplugged/config/settings/database_proxy.py + +[report] +fail_under=20 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..a60e70717 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +csunplugged/build/* +csunplugged/temp/* +csunplugged/static/* +csunplugged/node_modules/* diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..004d0199e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.md text +*.conf text +*.py text +*.html text +*.css text +*.js text \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 000000000..80d8d4386 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,3 @@ +## Please [read our guidelines for contributing](http://cs-unplugged.readthedocs.io/en/latest/getting_started/contributing_guide.html) + +In addition to these guidelines, you agree to uphold our [Code of Conduct](http://cs-unplugged.readthedocs.io/en/latest/getting_started/code_of_conduct.html). diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..fa1555047 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,21 @@ +## Description + +Clearly and concisely describe the issue or suggestion here: + + + + + + + +### Checklist + +*Change the space in the box to an `x` for those you have completed. You can also fill these out after creating the issue.* + +- [ ] I have linked any relevant existing issues/suggestions in the description above (include `#???` in your description to reference an issue, where `???` is the issue number). + +If this is a code related issue, please include the following in your description: + +- [ ] Steps to reproduce the behavior +- [ ] The platform(s)/software you are encountering the issue on +- [ ] The behavior you expect to see, and the actual behavior diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..4ac3de469 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,28 @@ +## Proposed changes + +Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request: + + + + + + + +### Checklist + +*Change the space in the box to an `x` for those you have completed. +You can also fill these out after creating the pull request. +If you're unsure about any of them, don't hesitate to ask. +We're here to help! +This is simply a reminder of what we are going to look for before merging your change.* + +- [ ] I have read the contribution guidelines. +- [ ] I have linked any relevant existing issues/suggestions in the description above (include `#???` in your description to reference an issue, where `???` is the issue number). +- [ ] The pull request is requesting a merge into the `develop` branch. +- [ ] I have added necessary documentation (if appropriate). +- [ ] If I've added/modified/deleted a third-party system, I've updated the relevant license files. + +### Further comments + +If this is a relatively large or complex change, kick off the discussion by explaining why you chose the solution you did and what alternatives you considered, etc. +Feel free to add any images that might be helpful to understand the initial problem/solution. diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..8bd287ec4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,112 @@ +# Ignore app.yaml file that contains passwords, secret keys, etc +app*.yaml +!app-sample.yaml + +# Byte-compiled / optimized / DLL files +*__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env + +# virtualenv +.venv/ +venv/ +docker_venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject + +# Installed node modules +node_modules/ + +# vim files +*.swp +*.swo + +# Custom git settings +.gitconfig + +# Temporary folder +temp/ + +# Collected static files folder +staticfiles/ diff --git a/.pyup.yml b/.pyup.yml new file mode 100644 index 000000000..a1e096229 --- /dev/null +++ b/.pyup.yml @@ -0,0 +1,6 @@ +# Settings for pyup bot +# Details here: https://pyup.io/docs/configuration/ + +branch: develop +label_prs: backend +branch_prefix: pyup/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..59d06b199 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,44 @@ +sudo: required +services: +- docker +env: +- DOCKER_COMPOSE_VERSION=1.11.2 +language: python +python: +- '3.6' +install: +- sudo apt-get update +- sudo apt-get -y -o Dpkg::Options::="--force-confnew" install docker-engine +- sudo rm /usr/local/bin/docker-compose +- curl -L "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" > docker-compose +- chmod +x docker-compose +- sudo mv docker-compose /usr/local/bin +- sudo docker --version +- sudo docker-compose --version +before_script: +- "./csu start" +script: +- "./csu dev test" +- "./csu dev test_backwards" +- "./csu dev test_coverage" +- "./csu dev style" +- "./csu dev docs" +deploy: + - provider: script + script: bash ./infrastructure/dev-deploy/dev-deploy.sh + skip_cleanup: true + on: + branch: develop + - provider: script + script: bash ./infrastructure/prod-deploy/prod-deploy.sh + skip_cleanup: true + on: + branch: master +after_script: +- bash <(curl -s https://codecov.io/bash) +notifications: + email: false + slack: + rooms: deptfunstuff:abJKvzApk5SKtcEyAgtswXAv + on_success: change + on_failure: change diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..7ac04775a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +# This Dockerfile is based off the Google App Engine Python runtime image +# https://github.com/GoogleCloudPlatform/python-runtime +FROM gcr.io/google-appengine/python + +# Add metadata to Docker image +LABEL maintainer="csse-education-research@canterbury.ac.nz" + +# Set terminal to be noninteractive +ARG DEBIAN_FRONTEND=noninteractive + +ENV DJANGO_PRODUCTION=True + +# Install packages, running of Python 3.4.2 +RUN apt-get update && apt-get install -y \ + python3 \ + python3-dev \ + python3-pip +RUN apt-get clean && rm /var/lib/apt/lists/*_* + +EXPOSE 8080 +RUN mkdir /csunplugged +WORKDIR /csunplugged + +# Copy and install Python dependencies +RUN python -m virtualenv --python=python3.4 /docker_venv +COPY requirements /requirements +RUN /docker_venv/bin/pip3 install -r /requirements/production.txt + +ADD ./csunplugged /csunplugged/ +CMD /docker_venv/bin/gunicorn -c gunicorn.conf.py -b :8080 config.wsgi diff --git a/Dockerfile-local b/Dockerfile-local new file mode 100644 index 000000000..bb4a37203 --- /dev/null +++ b/Dockerfile-local @@ -0,0 +1,48 @@ +# This Dockerfile is based off the Google App Engine Python runtime image +# https://github.com/GoogleCloudPlatform/python-runtime +FROM gcr.io/google-appengine/python + +# Add metadata to Docker image +LABEL maintainer="csse-education-research@canterbury.ac.nz" + +# Set terminal to be noninteractive +ARG DEBIAN_FRONTEND=noninteractive + +ENV DJANGO_PRODUCTION=False + +# Install packages (including Weasyprint dependencies), running of Python 3.4.2 +RUN apt-get update && apt-get install -y \ + python3 \ + python3-dev \ + python3-pip \ + libffi-dev \ + libcairo2-dev \ + python-dev \ + python-pip \ + python-lxml \ + python-cffi \ + libpango1.0-0 \ + libgdk-pixbuf2.0-0 \ + shared-mime-info +RUN apt-get clean && rm /var/lib/apt/lists/*_* + +EXPOSE 8080 + +# Copy and create virtual environment +COPY requirements /requirements +RUN python -m virtualenv --python=python3.4 /docker_venv + +# Install Weasyprint dependencies +RUN /docker_venv/bin/pip3 install -U pip setuptools +RUN /docker_venv/bin/pip3 install packaging==16.8 +RUN /docker_venv/bin/pip3 install appdirs==1.4.3 +RUN /docker_venv/bin/pip3 install html5lib==1.0b9 +RUN /docker_venv/bin/pip3 install six==1.10.0 +RUN /docker_venv/bin/pip3 install weasyprint==0.36 + +# Install dependencies +RUN /docker_venv/bin/pip3 install -r /requirements/local.txt + +RUN mkdir /cs-unplugged/ +RUN mkdir /cs-unplugged/csunplugged/ +WORKDIR /cs-unplugged/csunplugged/ diff --git a/LICENCE b/LICENCE new file mode 100644 index 000000000..a472b2d64 --- /dev/null +++ b/LICENCE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016-2017 University of Canterbury Computer Science Education Research Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENCE-CONTENT b/LICENCE-CONTENT new file mode 100644 index 000000000..406305248 --- /dev/null +++ b/LICENCE-CONTENT @@ -0,0 +1,432 @@ +Attribution-ShareAlike 4.0 International + +This license applies to the following contents of this project: + +- Markdown files located within the `csunplugged/topics/content/` directory. +- Images located within the `csunplugged/static/img/` directory. + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + + including for purposes of Section 3(b); and + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/LICENCE-THIRD-PARTY b/LICENCE-THIRD-PARTY new file mode 100644 index 000000000..f1bd71441 --- /dev/null +++ b/LICENCE-THIRD-PARTY @@ -0,0 +1,236 @@ +CS Unplugged uses third-party libraries or other resources that may +be distributed under licenses different than the CS Unplugged license. + +A summary of all third-party licenses are listed below, with a full copy of +each license available within the 'third-party-licenses' directory. + +If a particular source file of a library is included with the repository, it +should contain a copyright notice at the top of the file. + +In the event that we accidentally failed to list a required license, +please bring it to our attention through the issue tracker here: +https://github.com/uccser/cs-unplugged/issues/new + +============================================================================== +Bootstrap v4 +------------------------------------------------------------------------------ +https://getbootstrap.com/ +Copyright 2011-2017 The Bootstrap Authors. +Copyright 2011-2017 Twitter, Inc. +Licensed under MIT License. +third-party-licenses/bootstrap.txt +============================================================================== + +============================================================================== +coverage +------------------------------------------------------------------------------ +https://www.djangoproject.com/ +Copyright 2001 Gareth Rees. All rights reserved. +Copyright 2004-2017 Ned Batchelder. All rights reserved. +Licensed under Apache License Version 2.0 License. +third-party-licenses/coverage.txt +============================================================================== + +============================================================================== +details-element-polyfill +------------------------------------------------------------------------------ +https://github.com/javan/details-element-polyfill +Copyright (c) 2017 Javan Makhmali +Licensed under MIT License. +third-party-licenses/details-element-polyfill.txt +============================================================================== + +============================================================================== +Django +------------------------------------------------------------------------------ +https://www.djangoproject.com/ +Copyright (c) Django Software Foundation and individual contributors. +Licensed under BSD 3-clause "New" or "Revised" License. +third-party-licenses/django.txt +============================================================================== + +============================================================================== +django-bootstrap-breadcrumbs +------------------------------------------------------------------------------ +https://github.com/prymitive/bootstrap-breadcrumbs +Copyright (c) 2014 Łukasz Mierzwa. +Licensed under MIT License. +third-party-licenses/django-bootstrap-breadcrumbs.txt +============================================================================== + +============================================================================== +django-debug-toolbar +------------------------------------------------------------------------------ +https://django-debug-toolbar.readthedocs.io/en/stable/ +Copyright (c) Rob Hudson and individual contributors. +Licensed under BSD 3-clause "New" or "Revised" License License. +third-party-licenses/django-debug-toolbar.txt +============================================================================== + +============================================================================== +django-environ +------------------------------------------------------------------------------ +http://django-environ.readthedocs.org/ +Copyright (c) 2013-2017, Daniele Faraglia. +Licensed under MIT License. +third-party-licenses/django-environ.txt +============================================================================== + +============================================================================== +django-extensions +------------------------------------------------------------------------------ +https://django-extensions.readthedocs.io/ +Copyright (c) 2007 Michael Trier. +Licensed under MIT License. +third-party-licenses/django-extensions.txt +============================================================================== + +============================================================================== +flake8 +------------------------------------------------------------------------------ +http://flake8.pycqa.org/en/latest/ +Copyright (C) 2011-2013 Tarek Ziade +Copyright (C) 2012-2016 Ian Cordasco +Licensed under MIT License. +third-party-licenses/flake8.txt +============================================================================== + +============================================================================== +gevent +------------------------------------------------------------------------------ +https://github.com/gevent/gevent +Copyright Denis Bilenko and the contributors, http://www.gevent.org. +Licensed under MIT License. +third-party-licenses/gevent.txt +============================================================================== + +============================================================================== +gunicorn +------------------------------------------------------------------------------ +http://gunicorn.org/ +2009-2017 (c) Benoît Chesneau +2009-2015 (c) Paul J. Davis +Licensed under MIT License. +third-party-licenses/gunicorn.txt +============================================================================== + +============================================================================== +jQuery +------------------------------------------------------------------------------ +https://jquery.com/ +Copyright JS Foundation and other contributors, https://js.foundation/ +Licensed under MIT License. +third-party-licenses/jquery.txt +============================================================================== + +============================================================================== +NotoSans +------------------------------------------------------------------------------ +https://www.google.com/get/noto/ +Licensed under SIL Open Font License, Version 1.1. +third-party-licenses/noto-sans.txt +============================================================================== + +============================================================================== +Patrick Hand +------------------------------------------------------------------------------ +https://fonts.google.com/specimen/Patrick+Hand +http://www.patrickwagesreiter.at +Copyright (c) 2012 Patrick Wagesreiter +Licensed under SIL Open Font License, Version 1.1. +third-party-licenses/patrick-hand.txt +============================================================================== + +============================================================================== +Pillow +------------------------------------------------------------------------------ +https://python-pillow.org/ +The Python Imaging Library (PIL) is: +- Copyright © 1997-2011 by Secret Labs AB +- Copyright © 1995-2011 by Fredrik Lundh +Pillow is the friendly PIL fork. It is: +- Copyright © 2010-2017 by Alex Clark and contributors +Like PIL, Pillow is licensed under the open source PIL Software License. +third-party-licenses/pillow.txt +============================================================================== + +============================================================================== +psycopg2 +------------------------------------------------------------------------------ +http://initd.org/psycopg/ +Licensed under GNU Lesser General Public License. +third-party-licenses/psycopg2.txt +============================================================================== + +============================================================================== +pydocstyle +------------------------------------------------------------------------------ +https://github.com/PyCQA/pydocstyle/ +Copyright (c) 2012 GreenSteam, +Copyright (c) 2014-2017 Amir Rachum, +Licensed under MIT License. +third-party-licenses/pydocstyle.txt +============================================================================== + +============================================================================== +python-markdown-math +------------------------------------------------------------------------------ +https://github.com/mitya57/python-markdown-math +Copyright 2015-2017 Dmitry Shachnev . +Licensed under BSD 3-clause "New" or "Revised" License. +third-party-licenses/python-markdown-math.txt +============================================================================== + +============================================================================== +PyYAML +------------------------------------------------------------------------------ +http://pyyaml.org/ +Copyright (c) 2006 Kirill Simonov. +Licensed under MIT License. +third-party-licenses/pyyaml.txt +============================================================================== + +============================================================================== +Sphinx +------------------------------------------------------------------------------ +http://www.sphinx-doc.org/en/stable/ +Copyright (c) 2007-2017 by the Sphinx team. +Licensed under BSD License. +third-party-licenses/sphinx.txt +============================================================================== + +============================================================================== +sphinx_rtd_theme +------------------------------------------------------------------------------ +https://github.com/rtfd/sphinx_rtd_theme +Copyright (c) 2013 Dave Snider +Licensed under MIT License. +third-party-licenses/sphinx-rtd-theme.txt +============================================================================== + +============================================================================== +StickyState +------------------------------------------------------------------------------ +https://github.com/soenkekluth/sticky-state +Copyright Sönke Kluth (http://soenkekluth.com/). +Licensed under MIT License. +third-party-licenses/sticky-state.txt +============================================================================== + +============================================================================== +verto +------------------------------------------------------------------------------ +https://github.com/uccser/verto +Copyright (c) 2016-2017 University of Canterbury Computer Science Education Research and contributors. +Licensed under MIT License. +third-party-licenses/verto.txt +============================================================================== + +============================================================================== +wheel +------------------------------------------------------------------------------ +https://bitbucket.org/pypa/wheel/ +Copyright (c) 2012-2014 Daniel Holth and contributors. +Licensed under MIT License. +third-party-licenses/wheel.txt +============================================================================== diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 491278eb4..000000000 --- a/LICENSE.md +++ /dev/null @@ -1,17 +0,0 @@ -# License - -Computer Science Unplugged by University of Canterbury Computer Science Education Research Group is licensed under a [Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license](https://creativecommons.org/licenses/by-sa/4.0/). - -This means you are free to: - -- **Share:** copy and redistribute the material in any medium or format -- **Adapt:** remix, transform, and build upon the material - -for any purpose, even commercially. - -However you must obey the following terms: - -- **Attribution:** You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. -- **ShareAlike:** If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. - -This is a human-readable summary of (and not a substitute for) the [full license](https://creativecommons.org/licenses/by-sa/4.0/legalcode). This deed highlights only some of the key features and terms of the actual license. It is not a license and has no legal value. You should carefully review all of the terms and conditions of the actual license before using the licensed material. diff --git a/README.md b/README.md new file mode 100644 index 000000000..3996a8ebc --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +![Computer Science Unplugged](https://cloud.githubusercontent.com/assets/8001048/25562071/9c90501a-2dcf-11e7-959a-bf15dfee8362.png) + +Computer Science Unplugged is a collection of free learning activities that +teach Computer Science through engaging games and puzzles that use cards, +string, crayons and lots of running around. +The activities introduce students to Computational Thinking through concepts +such as binary numbers, algorithms, and data compression, separated from the +distractions and technical details of having to use computers. + +The CS Unplugged project is available at +[csunplugged.org](http://csunplugged.org/). +This version (considered as version 2.0) is not currently deployed in a +production environment. +A development build (`develop` branch) of this repository can be viewed at +[cs-unplugged-develop.appspot.com](http://cs-unplugged-develop.appspot.com/). + +## Documentation + +Documentation for this project can be found on +[ReadTheDocs](http://cs-unplugged.readthedocs.io/en/latest/), +and can also +be built from the documentation source within the `docs/` directory. + +## Contributing + +We would love your help to make this guide the best it can be! +Please read our +[contribution guide](http://cs-unplugged.readthedocs.io/en/latest/getting_started/contributing_guide.html) +to get started. + +## License + +The content of this project itself is licensed under the +[Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license](https://creativecommons.org/licenses/by-sa/4.0/) +(`LICENSE-CONTENT` file). +This license applies to the following contents of this project: + +- Markdown files located within the `csunplugged/topics/content/` directory. +- Images located within the `csunplugged/static/img/` directory. + +Third-party libraries used in this project have their license's +listed within the `LICENSE-THIRD-PARTY` file, with a full copy of the license +available within the `third-party-licenses` directory. +If a source file of a third-party library or system is stored within this +repository, then a copyright notice should be present at the top of the file. + +The rest of the project, which is the underlying source code used to manage +and display this content, is licensed under the +[MIT license](https://opensource.org/licenses/MIT) (`LICENSE` file). diff --git a/README.rst b/README.rst deleted file mode 100644 index e1acfbb96..000000000 --- a/README.rst +++ /dev/null @@ -1,37 +0,0 @@ -.. image:: https://cloud.githubusercontent.com/assets/8001048/25562071/9c90501a-2dcf-11e7-959a-bf15dfee8362.png - :alt: Computer Science Unplugged - :align: center - -Computer Science Unplugged is a collection of free learning activities that -teach Computer Science through engaging games and puzzles that use cards, -string, crayons and lots of running around. -The activities introduce students to Computational Thinking through concepts -such as binary numbers, algorithms, and data compression, separated from the -distractions and technical details of having to use computers. - -The latest release (``master`` branch) can be viewed at `csunplugged.org`_. -The development build (``develop`` branch) can be viewed at `cs-unplugged-develop.appspot.com`_. - -Documentation -============================================================================== - -Documentation for this project can be found on `ReadTheDocs`_, and can also -be built from the documentation source within the ``docs/`` directory. - -Contributing -============================================================================== - -We would love your help to make this guide the best it can be! -Please read our `contribution guide`_ to get started. - -License -============================================================================== - -CS Unplugged is licensed under a Creative Commons Attribution-ShareAlike 4.0 -International (CC BY-SA 4.0) license, see the `license file`_ for more details. - -.. _csunplugged.org: http://csunplugged.org/ -.. _cs-unplugged-develop.appspot.com: http://cs-unplugged-develop.appspot.com/en-gb/ -.. _contribution guide: http://cs-unplugged.readthedocs.io/en/latest/getting_started/contributing_guide.html -.. _license file: LICENSE -.. _ReadTheDocs: http://cs-unplugged.readthedocs.io/en/latest/ diff --git a/csu b/csu new file mode 100755 index 000000000..6f41ad5b7 --- /dev/null +++ b/csu @@ -0,0 +1,423 @@ +#!/bin/bash +# Helper script for commands related to the CS Unplugged repository. +# +# Notes: +# - Changes to template only require user to refresh browser. +# - Changes to static files require the 'static' command to be run. +# - Changes to Python code are detected by gunicorn and should take effect +# on the server after a few seconds. +# +# Future plans: +# - Start systems when a command is given (for example: 'static') when the +# development system has not yet been started. +# - When 'start' is run open website in default browser without creating +# new terminal prompt. + +# Constants +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' # No Color + +# Print out help information +function helpmenu { + printf "Script for performing tasks related to the CS Unplugged repository.\n\n" + printf "Usage: ./csu [COMMAND]\n" + printf "Replace [COMMAND] with a word from the list below.\n\n" + printf "COMMAND list:\n" + build_help + clean_help + printf " dev [DEV_COMMAND] Run a developer command.\n" + end_help + printf " help View all help.\n" + restart_help + start_help + update_help + wipe_help + printf "\nDEV_COMMAND list:\n" + documentation_help + flush_help + logs_help + makemigrations_help + makeresources_help + migrate_help + shell_help + static_help + static_prod_help + style_help + test_suite_help + test_suite_backwards_help + test_coverage_help + test_specific_help + updatedata_help +} + +# Start development environment +function start { + printf "Creating systems...\n" + docker-compose up -d + printf "\n" + # Run helper functions + update + # Alert user that system is ready + printf "\n${GREEN}Systems are ready!${NC}\n" + printf "Open your preferred web browser to the URL 'localhost'\n" +} +function start_help { + printf " start Start development environment (this also runs\n" + printf " the update command).\n" +} + +# Stop development environment +function end { + printf "Stopping systems... (takes roughly 10 to 20 seconds)\n" + docker-compose down + printf "\nDeleting system volumes...\n" + docker volume ls -qf dangling=true | xargs -r docker volume rm +} +function end_help { + printf " end Stop development environment.\n" +} + +# Stop and then restart development environment +function restart_help { + printf " restart Stop and then restart development environment.\n" +} + +# Run Django migrate and updatedata commands +function update { + static + + printf "\n" + migrate + + printf "\n" + updatedata + + static_scratch + collect_static +} +function update_help { + printf " update Run Django migrate and updatedata commands\n" + printf " and build static files.\n" +} + +# Run Django flush command +function flush { + docker-compose exec django /docker_venv/bin/python3 ./manage.py flush +} +function flush_help { + printf " flush Run Django flush command.\n" +} + +# Run Django makemigrations command +function makemigrations { + printf "Creating database migrations...\n" + docker-compose exec django /docker_venv/bin/python3 ./manage.py makemigrations +} +function makemigrations_help { + printf " makemigrations Run Django makemigrations command.\n" +} + +# Run Django makeresources command +function makeresources { + printf "Creating static resource PDFs...\n" + docker-compose exec django /docker_venv/bin/python3 ./manage.py makeresources +} +function makeresources_help { + printf " makeresources Run Django makeresources command.\n" +} + +# Run Django migrate command +function migrate { + printf "Applying database migrations...\n" + docker-compose exec django /docker_venv/bin/python3 ./manage.py migrate +} +function migrate_help { + printf " migrate Run Django migrate command.\n" +} + +# Run Django updatedata command +function updatedata { + printf "Loading topics content...\n" + docker-compose exec django /docker_venv/bin/python3 ./manage.py updatedata +} +function updatedata_help { + printf " updatedata Run updatedata command.\n" +} + +# Build Docker images +function build { + printf "Building Docker images...\n" + docker-compose build + printf "\nDeleting untagged images...\n" + docker images --no-trunc | grep '' | awk '{ print $3 }' | xargs -r docker rmi +} +function build_help { + printf " build Build or rebuild Docker images.\n" +} + +# Build static files +function static { + printf "Building static files...\n" + docker-compose exec nginx gulp build +} +function static_help { + printf " static Build static files.\n" +} + +# Build production static files +function static_prod { + printf "Building production static files...\n" + docker-compose exec nginx gulp build --production +} +function static_prod_help { + printf " static_prod Build production static files.\n" +} + +# Build scratch static files +function static_scratch { + printf "Building scratch images...\n" + docker-compose exec nginx gulp scratch +} + +# Collecting static files +function collect_static { + printf "\nCollecting static files..." + docker-compose exec django /docker_venv/bin/python3 ./manage.py collectstatic --no-input --clear +} + +# Run shell +function shell { + docker-compose exec django bash +} +function shell_help { + printf " shell Open shell to Django folder.\n" +} + +# Run style checks +function style { + printf "Running PEP8 style checker...\n" + docker-compose exec django /docker_venv/bin/flake8 + pep8_status=$? + printf "\nRunning Python docstring checker...\n" + docker-compose exec django /docker_venv/bin/pydocstyle --count --explain + pydocstyle_status=$? + ! (( $pep8_status || $pydocstyle_status )) +} +function style_help { + printf " style Run style checks.\n" +} + +# Run test suite +function test_suite { + printf "Running test suite...\n" + docker-compose exec django /docker_venv/bin/coverage run --rcfile=/cs-unplugged/.coveragerc ./manage.py test --settings=config.settings.testing --pattern "test_*.py" -v 3 +} +function test_suite_help { + printf " test Run test suite with code coverage.\n" +} + +# Run specific test suite +function test_specific { + printf "Running specific test suite...\n" + docker-compose exec django /docker_venv/bin/python3 ./manage.py test --settings=config.settings.testing "${1}" -v 3 +} +function test_specific_help { + printf " test_specific Run specific test suite. Pass in parameter\n" + printf " of Python test module.\n" +} + +# Display test coverage table +function test_coverage { + printf "Displaying test suite coverage...\n" + docker-compose exec django /docker_venv/bin/coverage xml -i + docker-compose exec django /docker_venv/bin/coverage report -m --skip-covered +} +function test_coverage_help { + printf " test_coverage Display code coverage report.\n" +} + +# Run test suite backwards for CI testing +function test_suite_backwards { + printf "Running test suite backwards...\n" + if [ "$TRAVIS_PULL_REQUEST" != "false" ] + then + docker-compose exec django /docker_venv/bin/python3 ./manage.py test --settings=config.settings.testing --pattern "test_*.py" --reverse -v 0 + fi +} +function test_suite_backwards_help { + printf " test_backwards Run test suite backwards.\n" +} + +# Generates the documentation (with warnings as errors) +function documentation { + printf "Removing any existing documentation...\n" + docker-compose exec django rm -rf /cs-unplugged/docs/build/ + docker-compose exec django mkdir /cs-unplugged/docs/build/ + printf "\nCreating documentation...\n" + docker-compose exec django /docker_venv/bin/sphinx-build -W /cs-unplugged/docs/source/ /cs-unplugged/docs/build/ +} +function documentation_help { + printf " docs Generate documentation.\n" +} + +# Delete all untagged dangling Docker images +function clean { + printf "If the following commands return an argument not found error,\n" + printf "this is because there is nothing to delete for clean up.\n" + + printf "\nDeleting unused volumes...\n" + docker volume ls -qf dangling=true | xargs -r docker volume rm + printf "\nDeleting exited containers...\n" + docker ps --filter status=dead --filter status=exited -aq | xargs docker rm -v + printf "\nDeleting dangling images...\n" + docker rmi $(docker images -f "dangling=true" -q) +} +function clean_help { + printf " clean Delete unused Docker files.\n" +} + +# Delete all Docker containers and images +function wipe { + docker rm $(docker ps -a -q) + docker rmi $(docker images -q) +} +function wipe_help { + printf " wipe Delete all Docker containers and images.\n" +} + +# View logs +function logs { + docker-compose logs +} +function logs_help { + printf " logs View logs.\n" +} + +function process_dev_command() { + if [ $# -lt 1 ] + then + printf "${RED}ERROR: dev command requires one parameter!${NC}\n" + helpmenu + exit 1 + else + case "$1" in + docs) + documentation + exit + ;; + flush) + flush + exit + ;; + logs) + logs + exit + ;; + makeresources) + makeresources + exit + ;; + makemigrations) + makemigrations + exit + ;; + migrate) + migrate + exit + ;; + shell) + shell + exit + ;; + static) + static + static_scratch + collect_static + exit + ;; + static_prod) + static_prod + static_scratch + collect_static + exit + ;; + style) + style + exit + ;; + test) + test_suite + exit + ;; + test_backwards) + test_suite_backwards + exit + ;; + test_coverage) + test_coverage + exit + ;; + test_specific) + test_specific $2 + exit + ;; + updatedata) + updatedata + exit + ;; + esac + fi +} + + +# If no command given +if [ $# -eq 0 ] +then + printf "${RED}ERROR: This script requires a command!${NC}\n" + helpmenu + exit 1 +fi +case "$1" in + build) + build + exit + ;; + clean) + clean + exit + ;; + dev) + process_dev_command $2 $3 + ;; + end) + end + exit + ;; + help) + helpmenu + exit + ;; + restart) + end + start + exit + ;; + start) + start + exit + ;; + update) + update + exit + ;; + wipe) + wipe + exit + ;; +esac + +# If no command triggered +printf "${RED}ERROR: Unknown command!${NC}\n" +printf "Type './csu help' for available commands.\n" +exit 1 diff --git a/csunplugged/config/__init__.py b/csunplugged/config/__init__.py new file mode 100644 index 000000000..484263a58 --- /dev/null +++ b/csunplugged/config/__init__.py @@ -0,0 +1,3 @@ +"""Module for Django system configuration.""" + +__version__ = "2.0.0-alpha.1" diff --git a/csunplugged/config/context_processors/version_number.py b/csunplugged/config/context_processors/version_number.py new file mode 100644 index 000000000..2eeb7207f --- /dev/null +++ b/csunplugged/config/context_processors/version_number.py @@ -0,0 +1,12 @@ +"""Context processor for displaying version number.""" + +from config import __version__ + + +def version_number(request): + """Return a dictionary containing system version number. + + Returns: + Dictionary containing version number to add to context. + """ + return {"VERSION_NUMBER": __version__} diff --git a/csunplugged/config/settings/__init__.py b/csunplugged/config/settings/__init__.py new file mode 100644 index 000000000..eae4f2b52 --- /dev/null +++ b/csunplugged/config/settings/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +"""Module for settings for Django system.""" diff --git a/csunplugged/config/settings/base.py b/csunplugged/config/settings/base.py new file mode 100644 index 000000000..c3d79bcd1 --- /dev/null +++ b/csunplugged/config/settings/base.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +""" +Base Django settings for CS Unplugged project. + +For more information on this file, see +https://docs.djangoproject.com/en/dev/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/dev/ref/settings/ +""" + +import environ + +# cs-unplugged/csunplugged/config/settings/base.py - 3 = csunplugged/ +ROOT_DIR = environ.Path(__file__) - 3 + +# Load operating system environment variables and then prepare to use them +env = environ.Env() + +# APP CONFIGURATION +# ---------------------------------------------------------------------------- +DJANGO_APPS = [ + # Default Django apps: + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "django.contrib.postgres", + + # Useful template tags + "django.contrib.humanize", + + # Admin + "django.contrib.admin", +] +THIRD_PARTY_APPS = [ + "django_bootstrap_breadcrumbs", +] + +# Apps specific for this project go here. +LOCAL_APPS = [ + "general.apps.GeneralConfig", + "topics.apps.TopicsConfig", + "resources.apps.ResourcesConfig", +] + +# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps +INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS + +# MIDDLEWARE CONFIGURATION +# ---------------------------------------------------------------------------- +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.locale.LocaleMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +# DEBUG +# ---------------------------------------------------------------------------- +# See: https://docs.djangoproject.com/en/dev/ref/settings/#debug +DEBUG = env.bool("DJANGO_DEBUG", False) + +# FIXTURE CONFIGURATION +# ---------------------------------------------------------------------------- +# See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS +FIXTURE_DIRS = ( + str(ROOT_DIR.path("fixtures")), +) + +# EMAIL CONFIGURATION +# ----------------------------------------------------------------------------- +# EMAIL_BACKEND = env("DJANGO_EMAIL_BACKEND", +# default="django.core.mail.backends.smtp.EmailBackend") + +# MANAGER CONFIGURATION +# ---------------------------------------------------------------------------- +# See: https://docs.djangoproject.com/en/dev/ref/settings/#admins +# ADMINS = [ +# ("University of Canterbury Computer Science Research Group", +# "csse-education@canterbury.ac.nz"), +# ] + +# See: https://docs.djangoproject.com/en/dev/ref/settings/#managers +# MANAGERS = ADMINS + +# GENERAL CONFIGURATION +# ---------------------------------------------------------------------------- +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# In a Windows environment this must be set to your system time zone. +TIME_ZONE = "UTC" + +# See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code +LANGUAGE_CODE = "en-us" + +# See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id +SITE_ID = 1 + +# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-i18n +USE_I18N = True + +# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-l10n +USE_L10N = True + +# See: https://docs.djangoproject.com/en/dev/ref/settings/#use-tz +USE_TZ = True + +# See: https://docs.djangoproject.com/en/dev/ref/settings/#locale-paths +LOCALE_PATHS = ["locale"] + +# TEMPLATE CONFIGURATION +# ---------------------------------------------------------------------------- +# See: https://docs.djangoproject.com/en/dev/ref/settings/#templates +TEMPLATES = [ + { + # See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND + "BACKEND": "django.template.backends.django.DjangoTemplates", + # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs + "DIRS": [ + str(ROOT_DIR.path("templates")), + ], + "OPTIONS": { + # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug + "debug": DEBUG, + # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders + # https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types + "loaders": [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", + ], + # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.template.context_processors.i18n", + "django.template.context_processors.media", + "django.template.context_processors.static", + "django.template.context_processors.tz", + "django.contrib.messages.context_processors.messages", + "config.context_processors.version_number.version_number", + ], + "libraries": { + "render_html_field": "config.templatetags.render_html_field", + }, + }, + }, +] + +# STATIC FILE CONFIGURATION +# ------------------------------------------------------------------------------ +# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root +STATIC_ROOT = str(ROOT_DIR.path("staticfiles")) + +# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url +STATIC_URL = "/staticfiles/" + +# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS +STATICFILES_DIRS = [ + str(ROOT_DIR.path("build")), +] + +# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders +STATICFILES_FINDERS = [ + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", +] + +# MEDIA CONFIGURATION +# ------------------------------------------------------------------------------ +# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root +MEDIA_ROOT = str(ROOT_DIR("media")) + +# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url +MEDIA_URL = "/media/" + +# URL Configuration +# ------------------------------------------------------------------------------ +ROOT_URLCONF = "config.urls" + +# See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application +WSGI_APPLICATION = "config.wsgi.application" + +# PASSWORD VALIDATION +# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators +# ------------------------------------------------------------------------------ + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] diff --git a/csunplugged/config/settings/database_proxy.py b/csunplugged/config/settings/database_proxy.py new file mode 100644 index 000000000..223a0c4ad --- /dev/null +++ b/csunplugged/config/settings/database_proxy.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +"""Django settings for connecting via Google Cloud SQL Proxy.""" + +from .base import * # noqa: F403 + + +# DATABASE CONFIGURATION +# ---------------------------------------------------------------------------- +# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "HOST": "127.0.0.1", + "PORT": "5433", + "NAME": "csunplugged", + "USER": env("GOOGLE_CLOUD_SQL_DATABASE_USERNAME"), # noqa: F405 + "PASSWORD": env("GOOGLE_CLOUD_SQL_DATABASE_PASSWORD"), # noqa: F405 + "ATOMIC_REQUESTS": True, + } +} + +SECRET_KEY = env("DJANGO_SECRET_KEY") # noqa: F405 diff --git a/csunplugged/config/settings/local.py b/csunplugged/config/settings/local.py new file mode 100644 index 000000000..bb7fa3a9d --- /dev/null +++ b/csunplugged/config/settings/local.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +""" +Django settings for local development environment. + +- Run in Debug mode +- Add custom dev application +- Add Django Debug Toolbar +- Add django-extensions +- Use console backend for emails +""" + +from .base import * # noqa: F403 + +# DATABASE CONFIGURATION +# ---------------------------------------------------------------------------- +# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases +DATABASES = { + "default": env.db("DATABASE_URL"), # noqa: F405 +} +DATABASES["default"]["ATOMIC_REQUESTS"] = True + +# DEBUG +# ---------------------------------------------------------------------------- +DEBUG = env.bool("DJANGO_DEBUG", default=True) # noqa: F405 +TEMPLATES[0]["OPTIONS"]["debug"] = DEBUG # noqa: F405 + +# SECRET CONFIGURATION +# ---------------------------------------------------------------------------- +# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key +# Note: This key only used for development and testing. +SECRET_KEY = env("DJANGO_SECRET_KEY", default="l@@)w&&%&u37+sjz^lsx^+29y_333oid3ygxzucar^8o(axo*f") # noqa: F405 + +# Mail settings +# ---------------------------------------------------------------------------- + +EMAIL_PORT = 1025 + +EMAIL_HOST = "localhost" +EMAIL_BACKEND = env("DJANGO_EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend") # noqa: F405 + + +# CACHING +# ---------------------------------------------------------------------------- +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "" + } +} + +# django-debug-toolbar +# ---------------------------------------------------------------------------- +MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware", ] # noqa: F405 +INSTALLED_APPS += ["debug_toolbar", ] # noqa: F405 + +INTERNAL_IPS = ["127.0.0.1", "10.0.2.2", ] + + +def show_django_debug_toolbar(request): + """Show Django Debug Toolbar in every request when running locally. + + Args: + request: The request object. + """ + return True + + +DEBUG_TOOLBAR_CONFIG = { + "DISABLE_PANELS": [ + "debug_toolbar.panels.redirects.RedirectsPanel", + ], + "SHOW_TEMPLATE_CONTEXT": True, + "SHOW_TOOLBAR_CALLBACK": show_django_debug_toolbar, + +} + +# django-extensions +# ---------------------------------------------------------------------------- +INSTALLED_APPS += ["django_extensions", ] + +# TESTING +# ---------------------------------------------------------------------------- +TEST_RUNNER = "django.test.runner.DiscoverRunner" + + +# Your local stuff: Below this line define 3rd party library settings +# ---------------------------------------------------------------------------- +INSTALLED_APPS += ["dev.apps.DevConfig"] # noqa: F405 diff --git a/csunplugged/config/settings/production.py b/csunplugged/config/settings/production.py new file mode 100644 index 000000000..30dc4a954 --- /dev/null +++ b/csunplugged/config/settings/production.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +""" +Django settings for production environment. + +- Load secret values from environment variables. +- Set static URL to Google Cloud Storage Bucket. +""" + +from .base import * # noqa: F403 + + +# SECRET CONFIGURATION +# ------------------------------------------------------------------------------ +# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key +# Raises ImproperlyConfigured exception if DJANGO_SECRET_KEY not in os.environ +SECRET_KEY = env("DJANGO_SECRET_KEY") # noqa: F405 + +# SECURITY WARNING: App Engine"s security features ensure that it is safe to +# have ALLOWED_HOSTS = ["*"] when the app is deployed. If you deploy a Django +# app not on App Engine, make sure to set an appropriate host here. +# See https://docs.djangoproject.com/en/1.10/ref/settings/ +ALLOWED_HOSTS = ["*"] + +# DATABASE CONFIGURATION +# ---------------------------------------------------------------------------- +# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": "csunplugged", + "USER": env("GOOGLE_CLOUD_SQL_DATABASE_USERNAME"), # noqa: F405 + "PASSWORD": env("GOOGLE_CLOUD_SQL_DATABASE_PASSWORD"), # noqa: F405 + "HOST": "/cloudsql/" + env("GOOGLE_CLOUD_SQL_CONNECTION_NAME"), # noqa: F405 + } +} +DATABASES["default"]["ATOMIC_REQUESTS"] = True + +# Static files +STATIC_URL = "https://storage.googleapis.com/" + env("GOOGLE_CLOUD_STORAGE_BUCKET_NAME") + "/static/" # noqa: F405 diff --git a/csunplugged/config/settings/testing.py b/csunplugged/config/settings/testing.py new file mode 100644 index 000000000..7ba94b102 --- /dev/null +++ b/csunplugged/config/settings/testing.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +"""Settings for running on continuous integration server.""" + +from .base import * # noqa: F403 + + +# DATABASE CONFIGURATION +# ---------------------------------------------------------------------------- +# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases +DATABASES = { + 'default': env.db('DATABASE_URL'), # noqa: F405 +} +DATABASES['default']['ATOMIC_REQUESTS'] = True + +# DEBUG +# ---------------------------------------------------------------------------- +# Turn debug off so tests run faster +DEBUG = False +TEMPLATES[0]["OPTIONS"]["debug"] = False # noqa: F405 + +# SECRET CONFIGURATION +# ---------------------------------------------------------------------------- +# See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key +# Note: This key only used for development and testing. +SECRET_KEY = env("DJANGO_SECRET_KEY", default="l@@)w&&%&u37+sjz^lsx^+29y_333oid3ygxzucar^8o(axo*f") # noqa: F405 + +# Mail settings +# ---------------------------------------------------------------------------- +EMAIL_HOST = "localhost" +EMAIL_PORT = 1025 + +# In-memory email backend stores messages in django.core.mail.outbox +# for unit testing purposes +EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" + +# CACHING +# ---------------------------------------------------------------------------- +# Speed advantages of in-memory caching without having to run Memcached +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + "LOCATION": "" + } +} + +# TESTING +# ---------------------------------------------------------------------------- +TEST_RUNNER = "django.test.runner.DiscoverRunner" + + +# PASSWORD HASHING +# ---------------------------------------------------------------------------- +# Use fast password hasher so tests run faster +PASSWORD_HASHERS = [ + "django.contrib.auth.hashers.MD5PasswordHasher", +] + +# TEMPLATE LOADERS +# ---------------------------------------------------------------------------- +# Keep templates in memory so tests run faster +TEMPLATES[0]["OPTIONS"]["loaders"] = [ # noqa: F405 + ["django.template.loaders.cached.Loader", [ + "django.template.loaders.filesystem.Loader", + "django.template.loaders.app_directories.Loader", + ], ], +] + +# Your local stuff: Below this line define 3rd party library settings +# ---------------------------------------------------------------------------- +INSTALLED_APPS += ["dev.apps.DevConfig"] # noqa: F405 diff --git a/csunplugged/config/templatetags/__init__.py b/csunplugged/config/templatetags/__init__.py new file mode 100644 index 000000000..940bebd21 --- /dev/null +++ b/csunplugged/config/templatetags/__init__.py @@ -0,0 +1 @@ +"""Module for the templatetags for the Django system.""" diff --git a/csunplugged/config/templatetags/render_html_field.py b/csunplugged/config/templatetags/render_html_field.py new file mode 100644 index 000000000..1841199c3 --- /dev/null +++ b/csunplugged/config/templatetags/render_html_field.py @@ -0,0 +1,59 @@ +"""Module for the custom render_html_field template tag.""" + +from django import template +from django.template import Template, Variable, Context, TemplateSyntaxError + +INVALID_ATTRIBUTE_MESSAGE = "The 'render_html_field' tag was given an " \ + "attribute that could not be converted to a string." + +MISSING_ATTRIBUTE_MESSAGE = "The 'render_html_field' tag was given an " \ + "attribute that does not exist." + + +class RenderHTMLFieldNode(template.Node): + """Class used for the custom render_html_field template tag.""" + + def __init__(self, item_to_be_rendered): + """Create the RenderHTMLFieldNode object.""" + self.item_to_be_rendered = Variable(item_to_be_rendered) + + def render(self, context): + """Render the text with the static template tag. + + Returns: + Rendered string of text, or raise an exception. + """ + try: + html = self.item_to_be_rendered.resolve(context) + return render_html_with_static(html, context) + except TypeError: + raise TemplateSyntaxError(INVALID_ATTRIBUTE_MESSAGE) + except template.VariableDoesNotExist: + raise TemplateSyntaxError(MISSING_ATTRIBUTE_MESSAGE) + + +def render_html_field(parser, token): + """Run when the render_html_field template tag is used. + + Returns: + Rendered string of text, or an empty string if the render + fails to convert. + """ + bits = token.split_contents() + if len(bits) != 2: + raise TemplateSyntaxError("'%s' takes only one argument" + " (a variable representing a template to render)" % bits[0]) + return RenderHTMLFieldNode(bits[1]) + + +def render_html_with_static(html, context=dict()): + """Render the HTML with the static template tag. + + Returns: + Rendered string of HTML. + """ + return Template("{% load static %}" + html).render(Context(context)) + + +register = template.Library() +render_html_field = register.tag(render_html_field) diff --git a/csunplugged/config/urls.py b/csunplugged/config/urls.py new file mode 100644 index 000000000..2391fa23f --- /dev/null +++ b/csunplugged/config/urls.py @@ -0,0 +1,31 @@ +"""URL configuration for the Django system. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/dev/topics/http/urls/ +""" + +from django.conf import settings +from django.conf.urls import include, url +from django.conf.urls.i18n import i18n_patterns +from django.contrib import admin +from general import views + +urlpatterns = i18n_patterns( + url(r"", include("general.urls", namespace="general")), + url(r"^topics/", include("topics.urls", namespace="topics")), + url(r"^resources/", include("resources.urls", namespace="resources")), + url(r"^admin/", include(admin.site.urls)), +) + +urlpatterns += [ + url(r"^_ah/health", views.health_check), +] + +if settings.DEBUG: + import debug_toolbar + urlpatterns += [ + url(r"^__debug__/", include(debug_toolbar.urls)), + ] + urlpatterns += i18n_patterns( + url(r"^__dev__/", include("dev.urls", namespace="dev")), + ) diff --git a/csunplugged/config/wsgi.py b/csunplugged/config/wsgi.py new file mode 100644 index 000000000..e6c9e1305 --- /dev/null +++ b/csunplugged/config/wsgi.py @@ -0,0 +1,23 @@ +""" +WSGI config for csunplugged project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks +# if running multiple sites in the same mod_wsgi process. To fix this, use +# mod_wsgi daemon mode with each site in its own daemon process, or use +# os.environ["DJANGO_SETTINGS_MODULE"] = "config.settings.production" +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production") + +# This application object is used by any WSGI server configured to use this +# file. This includes Django's development server, if the WSGI_APPLICATION +# setting points here. +application = get_wsgi_application() diff --git a/csunplugged/dev/__init__.py b/csunplugged/dev/__init__.py new file mode 100644 index 000000000..2429f52e9 --- /dev/null +++ b/csunplugged/dev/__init__.py @@ -0,0 +1 @@ +"""Module for the dev application.""" diff --git a/csunplugged/dev/apps.py b/csunplugged/dev/apps.py new file mode 100644 index 000000000..63f2887dd --- /dev/null +++ b/csunplugged/dev/apps.py @@ -0,0 +1,9 @@ +"""Application configuration for the dev application.""" + +from django.apps import AppConfig + + +class DevConfig(AppConfig): + """Configuration object for the dev application.""" + + name = "dev" diff --git a/csunplugged/dev/migrations/__init__.py b/csunplugged/dev/migrations/__init__.py new file mode 100644 index 000000000..e53e5429b --- /dev/null +++ b/csunplugged/dev/migrations/__init__.py @@ -0,0 +1 @@ +"""Module for migrations for the dev application.""" diff --git a/csunplugged/dev/urls.py b/csunplugged/dev/urls.py new file mode 100644 index 000000000..245f2b8f4 --- /dev/null +++ b/csunplugged/dev/urls.py @@ -0,0 +1,15 @@ +"""URL routing for the dev application.""" + +from django.conf.urls import url + +from . import views + +app_name = "dev" +urlpatterns = [ + # eg: /dev/ + url( + r"^$", + views.IndexView.as_view(), + name="index" + ), +] diff --git a/csunplugged/dev/views.py b/csunplugged/dev/views.py new file mode 100644 index 000000000..b56969681 --- /dev/null +++ b/csunplugged/dev/views.py @@ -0,0 +1,64 @@ +"""Views for the dev application.""" + +from django.views import generic +from utils.group_lessons_by_age import group_lessons_by_age +from topics.models import ( + Topic, + CurriculumArea, + CurriculumIntegration, + ProgrammingChallenge, + ProgrammingChallengeDifficulty, + ProgrammingChallengeLanguage, + LearningOutcome, + GlossaryTerm, +) + + +class IndexView(generic.TemplateView): + """View for the dev application homepage.""" + + template_name = "dev/index.html" + context_object_name = "all_topics" + + def get_context_data(self, **kwargs): + """Return context for dev homepage. + + Returns: + A dictionary of context data. + """ + context = super(IndexView, self).get_context_data(**kwargs) + + # Get topic, unit plan and lesson lists + context["topics"] = Topic.objects.order_by("name") + + # Build dictionaries for each unit plan and lesson + for topic in context["topics"]: + unit_plans = [] + for unit_plan in topic.unit_plans.all(): + unit_plan.grouped_lessons = group_lessons_by_age(unit_plan.lessons.all()) + unit_plans.append(unit_plan) + topic.units = unit_plans + topic.integrations = CurriculumIntegration.objects.filter(topic=topic).order_by("number") + topic.programming_challenges = ProgrammingChallenge.objects.filter(topic=topic).order_by( + "challenge_set_number", "challenge_number" + ) + + # Get curriculum area list + context["curriculum_areas"] = [] + for parent in CurriculumArea.objects.filter(parent=None): + children = [child for child in CurriculumArea.objects.filter(parent=parent)] + context["curriculum_areas"].append((parent, children)) + + # Get learning outcome list + context["learning_outcomes"] = LearningOutcome.objects.all() + + # Get learning outcome list + context["programming_challenge_languages"] = ProgrammingChallengeLanguage.objects.all() + + # Get learning outcome list + context["programming_challenge_difficulties"] = ProgrammingChallengeDifficulty.objects.all() + + # Get glossary term list + context["glossary_terms"] = GlossaryTerm.objects.all().order_by("term") + + return context diff --git a/csunplugged/docker-development-entrypoint.sh b/csunplugged/docker-development-entrypoint.sh new file mode 100755 index 000000000..98aeb069e --- /dev/null +++ b/csunplugged/docker-development-entrypoint.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +function postgres_ready(){ +/docker_venv/bin/python << END +import sys +import psycopg2 +try: + conn = psycopg2.connect(dbname="postgres", user="postgres", host="postgres") +except psycopg2.OperationalError: + sys.exit(-1) +sys.exit(0) +END +} + +until postgres_ready; do + >&2 echo "Postgres is unavailable - sleeping" + sleep 1 +done + +>&2 echo "Postgres is up - continuing..." + +# Start gunicorn service +echo "Starting gunicorn" +/docker_venv/bin/gunicorn -c ./gunicorn.conf.py -b :$PORT config.wsgi --reload diff --git a/csunplugged/general/__init__.py b/csunplugged/general/__init__.py new file mode 100644 index 000000000..f7b2e4e96 --- /dev/null +++ b/csunplugged/general/__init__.py @@ -0,0 +1 @@ +"""Module for the general application.""" diff --git a/csunplugged/general/apps.py b/csunplugged/general/apps.py new file mode 100644 index 000000000..4b6adc59b --- /dev/null +++ b/csunplugged/general/apps.py @@ -0,0 +1,9 @@ +"""Application configuration for the general application.""" + +from django.apps import AppConfig + + +class GeneralConfig(AppConfig): + """Configuration object for the general application.""" + + name = "general" diff --git a/csunplugged/general/management/__init__.py b/csunplugged/general/management/__init__.py new file mode 100644 index 000000000..40a2acad7 --- /dev/null +++ b/csunplugged/general/management/__init__.py @@ -0,0 +1 @@ +"""Module for the management of the general application.""" diff --git a/csunplugged/general/management/commands/__init__.py b/csunplugged/general/management/commands/__init__.py new file mode 100644 index 000000000..91c944e3a --- /dev/null +++ b/csunplugged/general/management/commands/__init__.py @@ -0,0 +1 @@ +"""Module for the custom commands for the general application.""" diff --git a/csunplugged/general/management/commands/updatedata.py b/csunplugged/general/management/commands/updatedata.py new file mode 100644 index 000000000..916e6281d --- /dev/null +++ b/csunplugged/general/management/commands/updatedata.py @@ -0,0 +1,15 @@ +"""Module for the custom Django updatedata command.""" + +from django.core import management + + +class Command(management.base.BaseCommand): + """Required command class for the custom Django updatedata command.""" + + help = "Update all data from content folders for all applications" + + def handle(self, *args, **options): + """Automatically called when the updatedata command is given.""" + management.call_command("flush", interactive=False) + management.call_command("loadresources") + management.call_command("loadtopics") diff --git a/csunplugged/general/migrations/__init__.py b/csunplugged/general/migrations/__init__.py new file mode 100644 index 000000000..76d16d04e --- /dev/null +++ b/csunplugged/general/migrations/__init__.py @@ -0,0 +1 @@ +"""Module for migrations for the general application.""" diff --git a/csunplugged/general/urls.py b/csunplugged/general/urls.py new file mode 100644 index 000000000..54f1fa8ff --- /dev/null +++ b/csunplugged/general/urls.py @@ -0,0 +1,38 @@ +"""URL routing for the general application.""" + +from django.conf.urls import url + +from . import views + +urlpatterns = [ + url( + r"^$", + views.GeneralIndexView.as_view(), + name="home" + ), + url( + r"^about/$", + views.GeneralAboutView.as_view(), + name="about" + ), + url( + r"^computational-thinking/$", + views.ComputationalThinkingView.as_view(), + name="computational_thinking" + ), + url( + r"^contact/$", + views.GeneralContactView.as_view(), + name="contact" + ), + url( + r"^people/$", + views.GeneralPeopleView.as_view(), + name="people" + ), + url( + r"^principles/$", + views.GeneralPrinciplesView.as_view(), + name="principles" + ), +] diff --git a/csunplugged/general/views.py b/csunplugged/general/views.py new file mode 100644 index 000000000..9cda8a587 --- /dev/null +++ b/csunplugged/general/views.py @@ -0,0 +1,49 @@ +"""Views for the general application.""" + +from django.views.generic import TemplateView +from django.http import HttpResponse + + +class GeneralIndexView(TemplateView): + """View for the homepage that renders from a template.""" + + template_name = "general/index.html" + + +class GeneralAboutView(TemplateView): + """View for the about page that renders from a template.""" + + template_name = "general/about.html" + + +class GeneralContactView(TemplateView): + """View for the contact page that renders from a template.""" + + template_name = "general/contact.html" + + +class GeneralPeopleView(TemplateView): + """View for the people page that renders from a template.""" + + template_name = "general/people.html" + + +class GeneralPrinciplesView(TemplateView): + """View for the princples page that renders from a template.""" + + template_name = "general/principles.html" + + +class ComputationalThinkingView(TemplateView): + """View for the Computational Thinking page that renders from a template.""" + + template_name = "general/computational-thinking.html" + + +def health_check(request): + """Return heath check response for Google App Engine. + + Returns a 200 HTTP response for Google App Engine to detect the system + is running. + """ + return HttpResponse(status=200) diff --git a/csunplugged/gulpfile.js b/csunplugged/gulpfile.js new file mode 100644 index 000000000..eec136ebf --- /dev/null +++ b/csunplugged/gulpfile.js @@ -0,0 +1,201 @@ +// gulp build : for a one off development build +// gulp build --production : for a minified production build + +'use strict'; +var gulp = require('gulp'); +var gutil = require('gulp-util'); +var del = require('del'); +var gulpif = require('gulp-if'); +var exec = require('child_process').exec; +var runSequence = require('run-sequence') +var notify = require('gulp-notify'); +var buffer = require('vinyl-buffer'); +var argv = require('yargs').argv; + +// sass +var sass = require('gulp-sass'); +var postcss = require('gulp-postcss'); +var postcssFlexbugFixes = require('postcss-flexbugs-fixes'); +var autoprefixer = require('autoprefixer'); +var sourcemaps = require('gulp-sourcemaps'); + +// linting +var jshint = require('gulp-jshint'); +var stylish = require('jshint-stylish'); + +// Scratch image rendering +var scratchblocks = require('scratchblocks'); +var rename = require("gulp-rename"); +var through = require('through2'); +var PluginError = require('gulp-util').PluginError; + +// gulp build --production +var production = !!argv.production; +// determine if we're doing a build +// and if so, bypass the livereload +var build = argv._.length ? argv._[0] === 'build' : true; + +// ---------------------------- +// Error notification methods +// ---------------------------- +var beep = function() { + var os = require('os'); + var file = 'gulp/error.wav'; + if (os.platform() === 'linux') { + // linux + exec("aplay " + file); + } else { + // mac + console.log("afplay " + file); + exec("afplay " + file); + } +}; +var handleError = function(task) { + return function(err) { + beep(); + + notify.onError({ + message: task + ' failed, check the logs..', + sound: false + })(err); + + gutil.log(gutil.colors.bgRed(task + ' error:'), gutil.colors.red(err)); + }; +}; + +var scratchSVG = function() { + var PLUGIN_NAME = 'scratchSVG'; + return through.obj(function(file, encoding, callback) { + if (file.isNull()) { + // nothing to do + return callback(null, file); + } + + if (file.isStream()) { + // file.contents is a Stream - https://nodejs.org/api/stream.html + this.emit('error', new PluginError(PLUGIN_NAME, 'Streams not supported!')); + } else if (file.isBuffer()) { + // file.contents is a Buffer - https://nodejs.org/api/buffer.html + var doc = scratchblocks.parse(file.contents.toString()) + doc.render(svg => { + var string = doc.exportSVGString(); + // Remove invalid xmlns attribute due to issue https://github.com/scratchblocks/scratchblocks/issues/219 + string = string.replace( + /, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2017-01-31 20:44+1300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: topics/templates/topics/index.html:3 +msgid "Topics" +msgstr "Thema" diff --git a/csunplugged/manage.py b/csunplugged/manage.py new file mode 100755 index 000000000..2a0efe264 --- /dev/null +++ b/csunplugged/manage.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +"""Command-line utility for Django administrative tasks.""" + +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/csunplugged/package.json b/csunplugged/package.json new file mode 100644 index 000000000..4db9ab7ac --- /dev/null +++ b/csunplugged/package.json @@ -0,0 +1,28 @@ +{ + "name": "cs-unplugged-assets", + "version": "0.0.2", + "main": "gulpfile.js", + "private": true, + "dependencies": { + "autoprefixer": "6.7.6", + "child_process": "1.0.2", + "del": "2.2.2", + "gulp": "3.9.1", + "gulp-if": "2.0.2", + "gulp-jshint": "2.0.4", + "gulp-notify": "3.0.0", + "gulp-postcss": "6.3.0", + "gulp-rename": "1.2.2", + "gulp-sass": "3.1.0", + "gulp-sourcemaps": "2.4.1", + "gulp-util": "3.0.8", + "jshint": "2.9.4", + "jshint-stylish": "2.2.1", + "postcss-flexbugs-fixes": "2.1.0", + "run-sequence": "1.2.2", + "scratchblocks": "3.1.2", + "through2": "2.0.3", + "vinyl-buffer": "1.0.0", + "yargs": "6.6.0" + } +} diff --git a/csunplugged/resources/__init__.py b/csunplugged/resources/__init__.py new file mode 100644 index 000000000..e05901c38 --- /dev/null +++ b/csunplugged/resources/__init__.py @@ -0,0 +1 @@ +"""Module for the resources application.""" diff --git a/csunplugged/resources/apps.py b/csunplugged/resources/apps.py new file mode 100644 index 000000000..b14b8092d --- /dev/null +++ b/csunplugged/resources/apps.py @@ -0,0 +1,9 @@ +"""Application configuration for the resources application.""" + +from django.apps import AppConfig + + +class ResourcesConfig(AppConfig): + """Configuration object for the resources application.""" + + name = "resources" diff --git a/csunplugged/resources/content/resources.yaml b/csunplugged/resources/content/resources.yaml new file mode 100644 index 000000000..19d496e15 --- /dev/null +++ b/csunplugged/resources/content/resources.yaml @@ -0,0 +1,54 @@ +sorting-network: + name: Sorting Network + webpage-template: resources/sorting-network.html + generation-view: sorting_network.py + thumbnail-static-path: img/resources/resource-sorting-network-thumbnail-example.png + copies: true +sorting-network-cards: + name: Sorting Network Cards + webpage-template: resources/sorting-network-cards.html + generation-view: sorting_network_cards.py + thumbnail-static-path: img/resources/sorting-network-cards/thumbnail.png + copies: false +treasure-hunt: + name: Treasure Hunt + webpage-template: resources/treasure-hunt.html + generation-view: treasure_hunt.py + thumbnail-static-path: img/resources/resource-treasure-hunt-thumbnail-example.png + copies: true +binary-cards: + name: Binary Cards + webpage-template: resources/binary-cards.html + generation-view: binary_cards.py + thumbnail-static-path: img/resources/binary-cards/thumbnail.png + copies: false +binary-cards-small: + name: Binary Cards (Small) + webpage-template: resources/binary-cards-small.html + generation-view: binary_cards_small.py + thumbnail-static-path: img/resources/binary-cards-small/thumbnail.png + copies: false +binary-windows: + name: Binary Windows + webpage-template: resources/binary-windows.html + generation-view: binary_windows.py + thumbnail-static-path: img/resources/binary-windows/thumbnail.png + copies: false +modulo-clock: + name: Modulo Clock + webpage-template: resources/modulo-clock.html + generation-view: modulo_clock.py + thumbnail-static-path: img/resources/modulo-clock/thumbnail.png + copies: false +binary-to-alphabet: + name: Binary to Alphabet + webpage-template: resources/binary-to-alphabet.html + generation-view: binary_to_alphabet.py + thumbnail-static-path: img/resources/binary-to-alphabet/thumbnail.png + copies: false +parity-cards: + name: Parity Cards + webpage-template: resources/parity-cards.html + generation-view: parity_cards.py + thumbnail-static-path: img/resources/parity-cards/thumbnail.png + copies: false diff --git a/csunplugged/resources/management/__init__.py b/csunplugged/resources/management/__init__.py new file mode 100644 index 000000000..631d515a6 --- /dev/null +++ b/csunplugged/resources/management/__init__.py @@ -0,0 +1 @@ +"""Module for the management of the resources application.""" diff --git a/csunplugged/resources/management/commands/_ResourcesLoader.py b/csunplugged/resources/management/commands/_ResourcesLoader.py new file mode 100644 index 000000000..c3876bdd4 --- /dev/null +++ b/csunplugged/resources/management/commands/_ResourcesLoader.py @@ -0,0 +1,60 @@ +"""Custom loader for loading resources.""" + +from django.db import transaction + +from utils.BaseLoader import BaseLoader + +from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError + +from resources.models import Resource + + +class ResourcesLoader(BaseLoader): + """Custom loader for loading resources.""" + + def __init__(self, structure_file, BASE_PATH): + """Create the loader for loading resources. + + Args: + structure_file: file path for structure YAML file (str). + BASE_PATH: base file path (str). + """ + super().__init__(BASE_PATH) + self.structure_file = structure_file + + @transaction.atomic + def load(self): + """Load the content for resources. + + Raise: + MissingRequiredFieldError: when no object can be found with the matching + attribute. + """ + resources_structure = self.load_yaml_file( + self.BASE_PATH.format( + self.structure_file + ) + ) + + for (resource_slug, resource_structure) in resources_structure.items(): + try: + resource_name = resource_structure["name"] + resource_template = resource_structure["webpage-template"] + resource_view = resource_structure["generation-view"] + resource_thumbnail = resource_structure["thumbnail-static-path"] + resource_copies = resource_structure["copies"] + except: + raise MissingRequiredFieldError() + + resource = Resource( + slug=resource_slug, + name=resource_name, + webpage_template=resource_template, + generation_view=resource_view, + thumbnail_static_path=resource_thumbnail, + copies=resource_copies, + ) + resource.save() + + self.log("Added Resource: {}".format(resource.name)) + self.log("All resources loaded!\n") diff --git a/csunplugged/resources/management/commands/__init__.py b/csunplugged/resources/management/commands/__init__.py new file mode 100644 index 000000000..782371f5d --- /dev/null +++ b/csunplugged/resources/management/commands/__init__.py @@ -0,0 +1 @@ +"""Module for the custom commands for the resources appliation.""" diff --git a/csunplugged/resources/management/commands/loadresources.py b/csunplugged/resources/management/commands/loadresources.py new file mode 100644 index 000000000..4f0dccb95 --- /dev/null +++ b/csunplugged/resources/management/commands/loadresources.py @@ -0,0 +1,22 @@ +"""Module for the custom Django loadresources command.""" + +from django.core.management.base import BaseCommand + +from utils.LoaderFactory import LoaderFactory + + +class Command(BaseCommand): + """Required command class for the custom Django loadresources command.""" + + help = "Reads resource data and adds to database" + + def handle(self, *args, **options): + """Automatically called when the loadresources command is given.""" + BASE_PATH = "resources/content/{}" + resource_structure_file = "resources.yaml" + factory = LoaderFactory() + + factory.create_resources_loader( + resource_structure_file, + BASE_PATH + ).load() diff --git a/csunplugged/resources/management/commands/makeresources.py b/csunplugged/resources/management/commands/makeresources.py new file mode 100644 index 000000000..92b990530 --- /dev/null +++ b/csunplugged/resources/management/commands/makeresources.py @@ -0,0 +1,46 @@ +"""Module for the custom Django makestaticresources command.""" + +import os +import os.path +import importlib +import itertools +from django.core.management.base import BaseCommand +from django.http.request import HttpRequest +from resources.models import Resource +from resources.views.generate_resource_pdf import generate_resource_pdf + + +class Command(BaseCommand): + """Required command class for the custom Django makestaticresources command.""" + + help = "Creates static PDF files of resource combinations." + + def handle(self, *args, **options): + """Automatically called when the makestaticresources command is given.""" + BASE_PATH = "staticfiles/resources/" + if not os.path.exists(BASE_PATH): + os.makedirs(BASE_PATH) + + for resource in Resource.objects.all(): + # Get path to resource module + resource_view = resource.generation_view + if resource_view.endswith(".py"): + resource_view = resource_view[:-3] + module_path = "resources.views.{}".format(resource_view) + # Save resource module + resource_module = importlib.import_module(module_path) + parameter_options = resource_module.valid_options() + parameter_option_keys = sorted(parameter_options) + combinations = [dict(zip(parameter_option_keys, product)) for product in itertools.product(*(parameter_options[parameter_option_key] for parameter_option_key in parameter_option_keys))] # noqa: E501 + # Create PDF for all possible combinations + for combination in combinations: + request = HttpRequest() + if resource.copies: + combination["copies"] = 30 + request.GET = combination + (pdf_file, filename) = generate_resource_pdf(request, resource, module_path) + filename = "{}.pdf".format(filename) + pdf_file_output = open(os.path.join(BASE_PATH, filename), "wb") + pdf_file_output.write(pdf_file) + pdf_file_output.close() + print("Created {}".format(filename)) diff --git a/csunplugged/resources/migrations/0001_initial.py b/csunplugged/resources/migrations/0001_initial.py new file mode 100644 index 000000000..c035f3d74 --- /dev/null +++ b/csunplugged/resources/migrations/0001_initial.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-21 06:23 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Resource', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('slug', models.SlugField(unique=True)), + ('name', models.CharField(max_length=200)), + ], + ), + ] diff --git a/csunplugged/resources/migrations/0002_resource_folder.py b/csunplugged/resources/migrations/0002_resource_folder.py new file mode 100644 index 000000000..4890f41df --- /dev/null +++ b/csunplugged/resources/migrations/0002_resource_folder.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-22 23:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('resources', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='resource', + name='folder', + field=models.CharField(default='', max_length=200), + preserve_default=False, + ), + ] diff --git a/csunplugged/resources/migrations/0003_resource_thumbnail.py b/csunplugged/resources/migrations/0003_resource_thumbnail.py new file mode 100644 index 000000000..6e4904c79 --- /dev/null +++ b/csunplugged/resources/migrations/0003_resource_thumbnail.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-26 06:08 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('resources', '0002_resource_folder'), + ] + + operations = [ + migrations.AddField( + model_name='resource', + name='thumbnail', + field=models.CharField(default='', max_length=200), + preserve_default=False, + ), + ] diff --git a/csunplugged/resources/migrations/0004_auto_20170226_0758.py b/csunplugged/resources/migrations/0004_auto_20170226_0758.py new file mode 100644 index 000000000..c3248f512 --- /dev/null +++ b/csunplugged/resources/migrations/0004_auto_20170226_0758.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-26 07:58 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('resources', '0003_resource_thumbnail'), + ] + + operations = [ + migrations.RenameField( + model_name='resource', + old_name='thumbnail', + new_name='thumbnail_static_path', + ), + ] diff --git a/csunplugged/resources/migrations/0005_auto_20170306_0305.py b/csunplugged/resources/migrations/0005_auto_20170306_0305.py new file mode 100644 index 000000000..f54391e7e --- /dev/null +++ b/csunplugged/resources/migrations/0005_auto_20170306_0305.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-06 03:05 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('resources', '0004_auto_20170226_0758'), + ] + + operations = [ + migrations.RenameField( + model_name='resource', + old_name='folder', + new_name='generation_view', + ), + migrations.AddField( + model_name='resource', + name='webpage_template', + field=models.CharField(default='', max_length=200), + preserve_default=False, + ), + ] diff --git a/csunplugged/resources/migrations/0006_resource_custom_header.py b/csunplugged/resources/migrations/0006_resource_custom_header.py new file mode 100644 index 000000000..a3dff5644 --- /dev/null +++ b/csunplugged/resources/migrations/0006_resource_custom_header.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-06 07:43 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('resources', '0005_auto_20170306_0305'), + ] + + operations = [ + migrations.AddField( + model_name='resource', + name='custom_header', + field=models.CharField(blank=True, max_length=200), + ), + ] diff --git a/csunplugged/resources/migrations/0007_remove_resource_custom_header.py b/csunplugged/resources/migrations/0007_remove_resource_custom_header.py new file mode 100644 index 000000000..6505c45ab --- /dev/null +++ b/csunplugged/resources/migrations/0007_remove_resource_custom_header.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-06 08:22 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('resources', '0006_resource_custom_header'), + ] + + operations = [ + migrations.RemoveField( + model_name='resource', + name='custom_header', + ), + ] diff --git a/csunplugged/resources/migrations/0008_resource_copies.py b/csunplugged/resources/migrations/0008_resource_copies.py new file mode 100644 index 000000000..6e2a196f3 --- /dev/null +++ b/csunplugged/resources/migrations/0008_resource_copies.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-18 06:34 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('resources', '0007_remove_resource_custom_header'), + ] + + operations = [ + migrations.AddField( + model_name='resource', + name='copies', + field=models.BooleanField(default=False), + preserve_default=False, + ), + ] diff --git a/csunplugged/resources/migrations/__init__.py b/csunplugged/resources/migrations/__init__.py new file mode 100644 index 000000000..d377da6a9 --- /dev/null +++ b/csunplugged/resources/migrations/__init__.py @@ -0,0 +1 @@ +"""Module for migrations for the resources application.""" diff --git a/csunplugged/resources/models.py b/csunplugged/resources/models.py new file mode 100644 index 000000000..cc3ec2681 --- /dev/null +++ b/csunplugged/resources/models.py @@ -0,0 +1,23 @@ +"""Models for the resources application.""" + +from django.db import models + + +class Resource(models.Model): + """Model for resource in database.""" + + # Auto-incrementing 'id' field is automatically set by Django + slug = models.SlugField(unique=True) + name = models.CharField(max_length=200) + webpage_template = models.CharField(max_length=200) + generation_view = models.CharField(max_length=200) + thumbnail_static_path = models.CharField(max_length=200) + copies = models.BooleanField() + + def __str__(self): + """Text representation of Resource object. + + Returns: + Name of resource (str). + """ + return self.name diff --git a/csunplugged/resources/urls.py b/csunplugged/resources/urls.py new file mode 100644 index 000000000..674952548 --- /dev/null +++ b/csunplugged/resources/urls.py @@ -0,0 +1,27 @@ +"""URL routing for the resources application.""" + +from django.conf.urls import url + +from .views import views + +app_name = "resources" +urlpatterns = [ + # eg: /resource/ + url( + r"^$", + views.IndexView.as_view(), + name="index" + ), + # eg: /resource/example-resource/ + url( + r"^(?P[-\w]+)/$", + views.resource, + name="resource" + ), + # eg: /resource/example-resource/generate/ + url( + r"^(?P[-\w]+)/generate$", + views.generate_resource, + name="generate" + ), +] diff --git a/csunplugged/resources/views/__init__.py b/csunplugged/resources/views/__init__.py new file mode 100644 index 000000000..2b5150371 --- /dev/null +++ b/csunplugged/resources/views/__init__.py @@ -0,0 +1 @@ +"""Views for the resource application.""" diff --git a/csunplugged/resources/views/binary_cards.py b/csunplugged/resources/views/binary_cards.py new file mode 100644 index 000000000..af24d2d43 --- /dev/null +++ b/csunplugged/resources/views/binary_cards.py @@ -0,0 +1,113 @@ +"""Module for generating Binary Cards resource.""" + +import os.path +from PIL import Image, ImageDraw, ImageFont +from utils.retrieve_query_parameter import retrieve_query_parameter + + +def resource_image(request, resource): + """Create a image for Binary Cards resource. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + A list of Pillow image objects (list). + """ + BASE_IMAGE_PATH = "static/img/resources/binary-cards/" + IMAGE_SIZE_X = 2480 + IMAGE_SIZE_Y = 3508 + IMAGE_DATA = [ + ("binary-cards-1-dot.png", 1), + ("binary-cards-2-dots.png", 2), + ("binary-cards-4-dots.png", 4), + ("binary-cards-8-dots.png", 8), + ("binary-cards-16-dots.png", 16), + ("binary-cards-32-dots.png", 32), + ("binary-cards-64-dots.png", 64), + ("binary-cards-128-dots.png", 128), + ] + + # Retrieve parameters + parameter_options = valid_options() + display_numbers = retrieve_query_parameter(request, "display_numbers", parameter_options["display_numbers"]) + black_back = retrieve_query_parameter(request, "black_back", parameter_options["black_back"]) + + if display_numbers: + font_path = "static/fonts/PatrickHand-Regular.ttf" + font = ImageFont.truetype(font_path, 600) + BASE_COORD_X = IMAGE_SIZE_X / 2 + BASE_COORD_Y = IMAGE_SIZE_Y - 100 + IMAGE_SIZE_Y = IMAGE_SIZE_Y + 300 + + images = [] + + for (image_path, number) in IMAGE_DATA: + image = Image.open(os.path.join(BASE_IMAGE_PATH, image_path)) + if display_numbers: + background = Image.new("RGB", (IMAGE_SIZE_X, IMAGE_SIZE_Y), "#FFF") + background.paste(image, mask=image) + draw = ImageDraw.Draw(background) + text = str(number) + text_width, text_height = draw.textsize(text, font=font) + coord_x = BASE_COORD_X - (text_width / 2) + coord_y = BASE_COORD_Y - (text_height / 2) + draw.text( + (coord_x, coord_y), + text, + font=font, + fill="#000" + ) + image = background + images.append(image) + + if black_back: + black_card = Image.new("1", (IMAGE_SIZE_X, IMAGE_SIZE_Y)) + images.append(black_card) + + return images + + +def subtitle(request, resource): + """Return the subtitle string of the resource. + + Used after the resource name in the filename, and + also on the resource image. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + text for subtitle (str). + """ + if retrieve_query_parameter(request, "display_numbers"): + display_numbers_text = "with numbers" + else: + display_numbers_text = "without numbers" + if retrieve_query_parameter(request, "black_back"): + black_back_text = "with black back" + else: + black_back_text = "without black back" + text = "{} - {} - {}".format( + display_numbers_text, + black_back_text, + retrieve_query_parameter(request, "paper_size") + ) + return text + + +def valid_options(): + """Provide dictionary of all valid parameters. + + This excludes the header text parameter. + + Returns: + All valid options (dict). + """ + return { + "display_numbers": [True, False], + "black_back": [True, False], + "paper_size": ["a4", "letter"], + } diff --git a/csunplugged/resources/views/binary_cards_small.py b/csunplugged/resources/views/binary_cards_small.py new file mode 100644 index 000000000..cb8feb2d4 --- /dev/null +++ b/csunplugged/resources/views/binary_cards_small.py @@ -0,0 +1,114 @@ +"""Module for generating Binary Cards (Small) resource.""" + +import os.path +from PIL import Image, ImageDraw, ImageFont +from utils.retrieve_query_parameter import retrieve_query_parameter + + +def resource_image(request, resource): + """Create a image for Binary Cards (Small) resource. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + A list of Pillow image objects (list). + """ + BASE_IMAGE_PATH = "static/img/resources/binary-cards-small/" + IMAGE_SIZE_X = 2480 + IMAGE_SIZE_Y = 3044 + IMAGE_DATA = [ + ("binary-cards-small-1.png", 4), + ("binary-cards-small-2.png", 8), + ("binary-cards-small-3.png", 12), + ] + + # Retrieve parameters + parameter_options = valid_options() + requested_bits = retrieve_query_parameter(request, "number_bits", parameter_options["number_bits"]) + dot_counts = retrieve_query_parameter(request, "dot_counts", parameter_options["dot_counts"]) + black_back = retrieve_query_parameter(request, "black_back", parameter_options["black_back"]) + + if dot_counts: + font_path = "static/fonts/PatrickHand-Regular.ttf" + font = ImageFont.truetype(font_path, 200) + TEXT_COORDS = [ + (525, 1341), + (1589, 1341), + (525, 2889), + (1589, 2889), + ] + + images = [] + + for (image_path, image_bits) in IMAGE_DATA: + requested_bits = int(requested_bits) + if image_bits <= requested_bits: + image = Image.open(os.path.join(BASE_IMAGE_PATH, image_path)) + if dot_counts: + draw = ImageDraw.Draw(image) + for number in range(image_bits - 4, image_bits): + text = str(pow(2, number)) + text_width, text_height = draw.textsize(text, font=font) + coord_x = TEXT_COORDS[number % 4][0] - (text_width / 2) + coord_y = TEXT_COORDS[number % 4][1] - (text_height / 2) + draw.text( + (coord_x, coord_y), + text, + font=font, + fill="#000" + ) + images.append(image) + + if black_back: + black_card = Image.new("1", (IMAGE_SIZE_X, IMAGE_SIZE_Y)) + images.append(black_card) + + return images + + +def subtitle(request, resource): + """Return the subtitle string of the resource. + + Used after the resource name in the filename, and + also on the resource image. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + text for subtitle (str). + """ + if retrieve_query_parameter(request, "dot_counts"): + display_numbers_text = "with dot counts" + else: + display_numbers_text = "without dot counts" + if retrieve_query_parameter(request, "black_back"): + black_back_text = "with black back" + else: + black_back_text = "without black back" + text = "{} bits - {} - {} - {}".format( + retrieve_query_parameter(request, "number_bits"), + display_numbers_text, + black_back_text, + retrieve_query_parameter(request, "paper_size") + ) + return text + + +def valid_options(): + """Provide dictionary of all valid parameters. + + This excludes the header text parameter. + + Returns: + All valid options (dict). + """ + return { + "number_bits": ["4", "8", "12"], + "dot_counts": [True, False], + "black_back": [True, False], + "paper_size": ["a4", "letter"], + } diff --git a/csunplugged/resources/views/binary_to_alphabet.py b/csunplugged/resources/views/binary_to_alphabet.py new file mode 100644 index 000000000..e41fc2551 --- /dev/null +++ b/csunplugged/resources/views/binary_to_alphabet.py @@ -0,0 +1,116 @@ +"""Module for generating Binary to Alphabet resource.""" + +from PIL import Image, ImageDraw, ImageFont +from utils.retrieve_query_parameter import retrieve_query_parameter + + +def resource_image(request, resource): + """Create a image for Binary to Alphabet resource. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + A Pillow image object. + """ + # Retrieve relevant image + parameter_options = valid_options() + worksheet_version = retrieve_query_parameter(request, "worksheet_version", parameter_options["worksheet_version"]) + if worksheet_version == "student": + image_path = "static/img/resources/binary-to-alphabet/table.png" + else: + image_path = "static/img/resources/binary-to-alphabet/table-teacher.png" + image = Image.open(image_path) + draw = ImageDraw.Draw(image) + + font_size = 30 + font_path = "static/fonts/PatrickHand-Regular.ttf" + font = ImageFont.truetype(font_path, font_size) + + # Draw headings + column_headings = ["Base 10", "Binary", "Letter"] + heading_coord_x = 18 + heading_coord_y = 6 + + i = 0 + while i < 9: # 9 = number of columns + + if i % 3 == 0: + text = str(column_headings[0]) + elif i % 3 == 1: + text = str(column_headings[1]) + else: + text = str(column_headings[2]) + + draw.text( + (heading_coord_x, heading_coord_y), + text, + font=font, + fill="#000" + ) + + heading_coord_x += 113 + + i += 1 + + # Draw numbers + # Column data: (min number, max number), x coord + columns_data = [((0, 9), 58), ((9, 18), 397), ((18, 27), 736)] + + for column_set in columns_data: + start, end = column_set[0] + base_coord_x = column_set[1] + base_coord_y = 75 + + for number in range(start, end): + text = str(number) + text_width, text_height = draw.textsize(text, font=font) + coord_x = base_coord_x - (text_width / 2) + coord_y = base_coord_y - (text_height / 2) + + draw.text( + (coord_x, coord_y), + text, + font=font, + fill="#000" + ) + + base_coord_y += 54 + + image = image.rotate(90, expand=True) + return image + + +def subtitle(request, resource): + """Return the subtitle string of the resource. + + Used after the resource name in the filename, and + also on the resource image. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + Text for subtitle (str). + """ + text = "{} - {}".format( + retrieve_query_parameter(request, "worksheet_version"), + retrieve_query_parameter(request, "paper_size") + ) + return text + + +def valid_options(): + """Provide dictionary of all valid parameters. + + This excludes the header text parameter. + + Returns: + All valid options (dict). + """ + return { + "worksheet_version": ["student", "teacher"], + "paper_size": ["a4", "letter"] + } diff --git a/csunplugged/resources/views/binary_windows.py b/csunplugged/resources/views/binary_windows.py new file mode 100644 index 000000000..5b33719ec --- /dev/null +++ b/csunplugged/resources/views/binary_windows.py @@ -0,0 +1,195 @@ +"""Module for generating Binary Windows resource.""" + +import os.path +from PIL import Image, ImageDraw, ImageFont +from utils.retrieve_query_parameter import retrieve_query_parameter + + +def resource_image(request, resource): + """Create a image for Binary Windows resource. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + A list of Pillow image objects (list). + """ + BASE_IMAGE_PATH = "static/img/resources/binary-windows/" + FONT_PATH = "static/fonts/PatrickHand-Regular.ttf" + FONT = ImageFont.truetype(FONT_PATH, 300) + SMALL_FONT = ImageFont.truetype(FONT_PATH, 180) + + # Retrieve parameters + parameter_options = valid_options() + number_of_bits = retrieve_query_parameter(request, "number_bits", parameter_options["number_bits"]) + value_type = retrieve_query_parameter(request, "value_type", parameter_options["value_type"]) + dot_counts = retrieve_query_parameter(request, "dot_counts", parameter_options["dot_counts"]) + + images = [] + page_sets = [("binary-windows-1-to-8.png", 8)] + if number_of_bits == "8": + page_sets.append(("binary-windows-16-to-128.png", 128)) + + for (filename, dot_count_start) in page_sets: + image = Image.open(os.path.join(BASE_IMAGE_PATH, filename)) + image = add_digit_values(image, value_type, True, 660, 724, 1700, FONT) + if dot_counts: + image = add_dot_counts(image, dot_count_start, SMALL_FONT) + image = image.rotate(90, expand=True) + images.append(image) + images.append(back_page(BASE_IMAGE_PATH, FONT, value_type)) + + return images + + +def back_page(BASE_IMAGE_PATH, FONT, value_type): + """Return a Pillow object of back page of Binary Windows. + + Args: + BASE_IMAGE_PATH: Base image path for finding images (str). + FONT: Pillow ImageFont for writing text (ImageFont). + value_type: Type of value representation used (str). + + Returns: + Pillow Image of back page (Image). + """ + image = Image.open(os.path.join(BASE_IMAGE_PATH, "binary-windows-blank.png")) + image = add_digit_values(image, value_type, False, 660, 724, 650, FONT) + image = image.rotate(90, expand=True) + return image + + +def add_dot_counts(image, starting_value, font): + """Add dot count text onto image. + + Args: + image: The image to add text to (Pillow Image). + starting_value: Number on left window (int). + font: Font used for adding text (Pillow Font). + + Returns: + Pillow Image with text added (Pillow Image). + """ + value = starting_value + draw = ImageDraw.Draw(image) + coord_x = 660 + coord_x_increment = 724 + coord_y = 1000 + for i in range(4): + text = str(value) + text_width, text_height = draw.textsize(text, font=font) + text_coord_x = coord_x - (text_width / 2) + text_coord_y = coord_y - (text_height / 2) + draw.text( + (text_coord_x, text_coord_y), + text, + font=font, + fill="#000" + ) + coord_x += coord_x_increment + value = int(value / 2) + return image + + +def add_digit_values(image, value_type, on, x_coord_start, x_coord_increment, base_y_coord, font): + """Add binary values onto image. + + Args: + image: The image to add binary values to (Pillow Image). + value_type: Either "binary" for 0's and 1's, or "lightbulb" for + lit and unlit lightbulbs (str). + on: True if binary value is on/lit, otherwise False (bool). + x_coord_start: X co-ordinate starting value (int). + x_coord_increment: X co-ordinate increment value (int). + base_y_coord: Y co-ordinate value (int). + font: Font used for adding text (Pillow Font). + + Returns: + Pillow Image with binary values (Pillow Image). + """ + text_coord_x = x_coord_start + + if value_type == "binary": + if on: + text = "1" + else: + text = "0" + elif value_type == "lightbulb": + if on: + image_file = "col_binary_lightbulb.png" + else: + image_file = "col_binary_lightbulb_off.png" + lightbulb = Image.open(os.path.join("static/img/topics/", image_file)) + (width, height) = lightbulb.size + SCALE_FACTOR = 0.6 + lightbulb = lightbulb.resize((int(width * SCALE_FACTOR), int(height * SCALE_FACTOR))) + (width, height) = lightbulb.size + lightbulb_width = int(width / 2) + lightbulb_height = int(height / 2) + if not on: + lightbulb = lightbulb.rotate(180) + + for i in range(4): + draw = ImageDraw.Draw(image) + if value_type == "binary": + + text_width, text_height = draw.textsize(text, font=font) + coord_x = text_coord_x - (text_width / 2) + coord_y = base_y_coord - (text_height / 2) + draw.text( + (coord_x, coord_y), + text, + font=font, + fill="#000" + ) + elif value_type == "lightbulb": + coords = (text_coord_x - lightbulb_width, base_y_coord - lightbulb_height + 75) + image.paste(lightbulb, box=coords, mask=lightbulb) + text_coord_x += x_coord_increment + return image + + +def subtitle(request, resource): + """Return the subtitle string of the resource. + + Used after the resource name in the filename, and + also on the resource image. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + text for subtitle (str). + """ + number_of_bits = retrieve_query_parameter(request, "number_bits") + value_type = retrieve_query_parameter(request, "value_type") + dot_counts = retrieve_query_parameter(request, "dot_counts") + if dot_counts: + count_text = "with dot counts" + else: + count_text = "without dot counts" + TEMPLATE = "{num_bits} bits - {value} - {counts}" + text = TEMPLATE.format( + num_bits=number_of_bits, + value=value_type, + counts=count_text + ) + return text + + +def valid_options(): + """Provide dictionary of all valid parameters. + + This excludes the header text parameter. + + Returns: + All valid options (dict). + """ + return { + "number_bits": ["4", "8"], + "value_type": ["binary", "lightbulb"], + "dot_counts": [True, False], + "paper_size": ["a4", "letter"], + } diff --git a/csunplugged/resources/views/generate_resource_pdf.py b/csunplugged/resources/views/generate_resource_pdf.py new file mode 100644 index 000000000..8fbaec093 --- /dev/null +++ b/csunplugged/resources/views/generate_resource_pdf.py @@ -0,0 +1,95 @@ +"""Module for generating custom resource PDFs.""" + +from django.http import Http404 +from django.template.loader import render_to_string +from django.contrib.staticfiles import finders +from django.conf import settings +from PIL import Image +from io import BytesIO +import importlib +import base64 + +MM_TO_PIXEL_RATIO = 3.78 + + +def generate_resource_pdf(request, resource, module_path): + """Return a response containing a generated PDF resource. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + module_path: Path to module for generating resource (str). + + Returns: + Tuple of PDF file of generated resource and filename. + """ + from weasyprint import HTML, CSS + context = dict() + get_request = request.GET + context["resource"] = resource + context["header_text"] = get_request.get("header_text", "") + context["paper_size"] = get_request.get("paper_size", None) + + if context["paper_size"] is None: + raise Http404("Paper size parameter not specified.") + + resource_image_generator = importlib.import_module(module_path) + num_copies = range(0, int(get_request.get("copies", 1))) + context["resource_images"] = [] + for copy in num_copies: + context["resource_images"].append( + generate_resource_image(get_request, resource, module_path) + ) + + filename = "{} ({})".format(resource.name, resource_image_generator.subtitle(get_request, resource)) + context["filename"] = filename + + pdf_html = render_to_string("resources/base-resource-pdf.html", context) + html = HTML(string=pdf_html, base_url=settings.STATIC_ROOT) + css_file = finders.find("css/print-resource-pdf.css") + css_string = open(css_file, encoding="UTF-8").read() + base_css = CSS(string=css_string) + return (html.write_pdf(stylesheets=[base_css]), filename) + + +def generate_resource_image(get_request, resource, module_path): + """Retrieve image(s) for one copy of resource from resource generator. + + Images are resized to size. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + module_path: Path to module for generating resource (str). + + Returns: + List of Base64 strings of a generated resource images for one copy. + """ + # Get images from resource image creator + resource_image_generator = importlib.import_module(module_path) + raw_images = resource_image_generator.resource_image(get_request, resource) + if not isinstance(raw_images, list): + raw_images = [raw_images] + + # Resize images to reduce file size + if get_request["paper_size"] == "a4": + max_pixel_height = 267 * MM_TO_PIXEL_RATIO + elif get_request["paper_size"] == "letter": + max_pixel_height = 249 * MM_TO_PIXEL_RATIO + + images = [] + for image in raw_images: + (width, height) = image.size + if height > max_pixel_height: + ratio = max_pixel_height / height + width *= ratio + height *= ratio + image = image.resize((int(width), int(height)), Image.ANTIALIAS) + + # Save image to buffer + image_buffer = BytesIO() + image.save(image_buffer, format="PNG") + # Add base64 of image to list of images + images.append(base64.b64encode(image_buffer.getvalue())) + + return images diff --git a/csunplugged/resources/views/modulo_clock.py b/csunplugged/resources/views/modulo_clock.py new file mode 100644 index 000000000..b6ba7b3a9 --- /dev/null +++ b/csunplugged/resources/views/modulo_clock.py @@ -0,0 +1,86 @@ +"""Module for generating Binary Cards resource.""" + +from math import pi, sin, cos +from PIL import Image, ImageDraw, ImageFont +from utils.retrieve_query_parameter import retrieve_query_parameter + + +def resource_image(request, resource): + """Create a image for Binary Cards resource. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + A list of Pillow image objects (list). + """ + parameter_options = valid_options() + modulo_number = int(retrieve_query_parameter(request, "modulo_number", parameter_options["modulo_number"])) + if modulo_number == 2: + image_path = "static/img/resources/modulo-clock/modulo-clock-2.png" + elif modulo_number == 10: + image_path = "static/img/resources/modulo-clock/modulo-clock-10.png" + image = Image.open(image_path) + draw = ImageDraw.Draw(image) + + font_size = 150 + font_path = "static/fonts/PatrickHand-Regular.ttf" + font = ImageFont.truetype(font_path, font_size) + + x_offset = 75 + y_offset = 225 + radius = 750 + x_center = (image.width - x_offset) / 2 + y_center = (image.height - y_offset) / 2 + start_angle = (2 * pi) / modulo_number + + for num in range(0, modulo_number): + + text = str(num) + + angle = start_angle * (num - 2.5) + x_coord = radius * cos(angle) + x_center + y_coord = radius * sin(angle) + y_center + + draw.text( + (x_coord, y_coord), + text, + font=font, + fill="#000" + ) + + return image + + +def subtitle(request, resource): + """Return the subtitle string of the resource. + + Used after the resource name in the filename, and + also on the resource image. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + Text for subtitle (str). + """ + return "{} - {}".format( + retrieve_query_parameter(request, "modulo_number"), + retrieve_query_parameter(request, "paper_size") + ) + + +def valid_options(): + """Provide dictionary of all valid parameters. + + This excludes the header text parameter. + + Returns: + All valid options (dict). + """ + return { + "modulo_number": ["2", "10"], + "paper_size": ["a4", "letter"] + } diff --git a/csunplugged/resources/views/parity_cards.py b/csunplugged/resources/views/parity_cards.py new file mode 100644 index 000000000..36cd43d0c --- /dev/null +++ b/csunplugged/resources/views/parity_cards.py @@ -0,0 +1,85 @@ +"""Module for generating Parity Cards resource.""" + +from PIL import Image, ImageDraw +from utils.retrieve_query_parameter import retrieve_query_parameter + + +def resource_image(request, resource): + """Create a image for Parity Cards resource. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + A list of Pillow image objects (list). + """ + CARDS_COLUMNS = 4 + CARDS_ROWS = 5 + CARD_SIZE = 500 + IMAGE_SIZE_X = CARD_SIZE * CARDS_COLUMNS + IMAGE_SIZE_Y = CARD_SIZE * CARDS_ROWS + LINE_COLOUR = "#000000" + LINE_WIDTH = 3 + + front_page = Image.new("RGB", (IMAGE_SIZE_X, IMAGE_SIZE_Y), "#fff") + draw = ImageDraw.Draw(front_page) + for x_coord in range(0, IMAGE_SIZE_X, CARD_SIZE): + draw.line([(x_coord, 0), (x_coord, IMAGE_SIZE_Y)], fill=LINE_COLOUR, width=LINE_WIDTH) + draw.line([(IMAGE_SIZE_X - 1, 0), (IMAGE_SIZE_X - 1, IMAGE_SIZE_Y)], fill=LINE_COLOUR, width=LINE_WIDTH) + for y_coord in range(0, IMAGE_SIZE_Y, CARD_SIZE): + draw.line([(0, y_coord), (IMAGE_SIZE_X, y_coord)], fill=LINE_COLOUR, width=LINE_WIDTH) + draw.line([(0, IMAGE_SIZE_Y - 1), (IMAGE_SIZE_X, IMAGE_SIZE_Y - 1)], fill=LINE_COLOUR, width=LINE_WIDTH) + + # Retrieve parameters + parameter_options = valid_options() + back_colour = retrieve_query_parameter(request, "back_colour", parameter_options["back_colour"]) + + if back_colour == "black": + back_colour_hex = "#000000" + elif back_colour == "blue": + back_colour_hex = "#3366ff" + elif back_colour == "green": + back_colour_hex = "#279f2d" + elif back_colour == "purple": + back_colour_hex = "#6e3896" + else: + back_colour_hex = "#cc0423" + + back_page = Image.new("RGB", (IMAGE_SIZE_X, IMAGE_SIZE_Y), back_colour_hex) + + return [front_page, back_page] + + +def subtitle(request, resource): + """Return the subtitle string of the resource. + + Used after the resource name in the filename, and + also on the resource image. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + text for subtitle (str). + """ + text = "{} back - {}".format( + retrieve_query_parameter(request, "back_colour"), + retrieve_query_parameter(request, "paper_size") + ) + return text + + +def valid_options(): + """Provide dictionary of all valid parameters. + + This excludes the header text parameter. + + Returns: + All valid options (dict). + """ + return { + "back_colour": ["black", "blue", "green", "purple", "red"], + "paper_size": ["a4", "letter"], + } diff --git a/csunplugged/resources/views/sorting_network.py b/csunplugged/resources/views/sorting_network.py new file mode 100644 index 000000000..fe3a286aa --- /dev/null +++ b/csunplugged/resources/views/sorting_network.py @@ -0,0 +1,113 @@ +"""Module for generating Sorting Network resource.""" + +from PIL import Image, ImageDraw, ImageFont +from random import sample +from utils.retrieve_query_parameter import retrieve_query_parameter + + +def resource_image(request, resource): + """Create a image for Sorting Network resource. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + A Pillow image object. + """ + image_path = "static/img/resources/resource-sorting-network-colour.png" + image = Image.open(image_path) + draw = ImageDraw.Draw(image) + + (range_min, range_max, font_size) = number_range(request) + + font_path = "static/fonts/PatrickHand-Regular.ttf" + + # Add numbers to text if needed + parameter_options = valid_options() + prefilled_values = retrieve_query_parameter(request, "prefilled_values", parameter_options["prefilled_values"]) + if prefilled_values != "blank": + font = ImageFont.truetype(font_path, font_size) + numbers = sample(range(range_min, range_max), 6) + base_coord_x = 70 + base_coord_y = 2560 + coord_x_increment = 204 + for number in numbers: + text = str(number) + text_width, text_height = draw.textsize(text, font=font) + coord_x = base_coord_x - (text_width / 2) + coord_y = base_coord_y - (text_height / 2) + draw.text( + (coord_x, coord_y), + text, + font=font, + fill="#000" + ) + base_coord_x += coord_x_increment + + return image + + +def subtitle(request, resource): + """Return the subtitle string of the resource. + + Used after the resource name in the filename, and + also on the resource image. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + text for subtitle (str). + """ + prefilled_values = retrieve_query_parameter(request, "prefilled_values") + if prefilled_values == "blank": + range_text = "blank" + else: + SUBTITLE_TEMPLATE = "{} to {}" + range_min, range_max, font_size = number_range(request) + range_text = SUBTITLE_TEMPLATE.format(range_min, range_max - 1) + return "{} - {}".format(range_text, retrieve_query_parameter(request, "paper_size")) + + +def number_range(request): + """Return number range tuple for resource. + + Args: + request: HTTP request object (HttpRequest). + + Returns: + Tuple of (range_min, range_max, font_size). + """ + parameter_options = valid_options() + prefilled_values = retrieve_query_parameter(request, "prefilled_values", parameter_options["prefilled_values"]) + range_min = 0 + range_max = 0 + font_size = 150 + if prefilled_values == "easy": + range_min = 1 + range_max = 10 + elif prefilled_values == "medium": + range_min = 10 + range_max = 100 + font_size = 120 + elif prefilled_values == "hard": + range_min = 100 + range_max = 1000 + font_size = 90 + return (range_min, range_max, font_size) + + +def valid_options(): + """Provide dictionary of all valid parameters. + + This excludes the header text parameter. + + Returns: + All valid options (dict). + """ + return { + "prefilled_values": ["blank", "easy", "medium", "hard"], + "paper_size": ["a4", "letter"], + } diff --git a/csunplugged/resources/views/sorting_network_cards.py b/csunplugged/resources/views/sorting_network_cards.py new file mode 100644 index 000000000..911003712 --- /dev/null +++ b/csunplugged/resources/views/sorting_network_cards.py @@ -0,0 +1,136 @@ +"""Module for generating Sorting Network Cards resource.""" + +from random import sample +from PIL import Image, ImageDraw, ImageFont +from utils.retrieve_query_parameter import retrieve_query_parameter + + +def resource_image(request, resource): + """Create a image for Sorting Network Cards resource. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + A list of Pillow image objects (list). + """ + IMAGE_SIZE_X = 2000 + IMAGE_SIZE_Y = 3000 + LINE_COLOUR = "#000000" + LINE_WIDTH = 3 + font_path = "static/fonts/PatrickHand-Regular.ttf" + + # Retrieve parameters + parameter_options = valid_options() + card_type = retrieve_query_parameter(request, "type", parameter_options["type"]) + + # Create card outlines + card_outlines = Image.new("RGB", (IMAGE_SIZE_X, IMAGE_SIZE_Y), "#fff") + draw = ImageDraw.Draw(card_outlines) + for x_coord in range(0, IMAGE_SIZE_X, IMAGE_SIZE_X - LINE_WIDTH): + draw.line([(x_coord, 0), (x_coord, IMAGE_SIZE_Y)], fill=LINE_COLOUR, width=LINE_WIDTH) + for y_coord in range(0, IMAGE_SIZE_Y, int(IMAGE_SIZE_Y / 2 - LINE_WIDTH)): + draw.line([(0, y_coord), (IMAGE_SIZE_X, y_coord)], fill=LINE_COLOUR, width=LINE_WIDTH) + + # Prepare text data + if card_type == "small_numbers": + font_size = 800 + text = ["1", "2", "3", "4", "5", "6"] + elif card_type == "large_numbers": + font_size = 500 + text = [] + numbers = sample(range(1700000, 2100000), 6) + for number in numbers: + text.append("{:,}".format(number)) + elif card_type == "fractions": + font_size = 900 + font_path = "static/fonts/NotoSans-Regular.ttf" + text = [u"\u00bd", u"\u2153", u"\u2154", u"\u215c", u"\u00be", u"\u215d"] + elif card_type == "maori_numbers": + font_size = 300 + text = [ + "tahi", "rua", "toru", "whā", "rima", "ono", "whitu", "waru", + "iwa", "tekau", "tekau mā tahi", "tekau mā waru", "tekau mā toru", + "tekau mā whā", "rua tekau", "rua tekau mā ono" + ] + elif card_type == "words": + font_size = 500 + text = ["crocodile", "crochet", "kiwi", "weka", "kiwi", "kiwano"] + elif card_type == "letters": + font_size = 800 + text = ["L", "O", "N", "K", "E", "D", "S", "P", "G", "B", "I", "Y"] + else: + font_size = 500 + text = [ + "whero", "kākāriki", "kiwikiwi", "karaka", + "kōwhai", "pango", "māwhero", "mā" + ] + + font = ImageFont.truetype(font_path, font_size) + card_centers = [ + (IMAGE_SIZE_X / 2, IMAGE_SIZE_Y / 4), + (IMAGE_SIZE_X / 2, (IMAGE_SIZE_Y / 4) * 3), + ] + + # Add text to cards + images = [] + for (text_number, text_string) in enumerate(text): + if text_number % 2 == 0: + page = card_outlines.copy() + draw = ImageDraw.Draw(page) + (x, y) = card_centers[0] + else: + (x, y) = card_centers[1] + + text_width, text_height = draw.textsize(text_string, font=font) + coord_x = x - (text_width / 2) + coord_y = y - (text_height / 1.5) + draw.text( + (coord_x, coord_y), + text_string, + font=font, + fill="#000" + ) + # If text on second card but not last page + if text_number % 2 == 1 and text_number != len(text) - 1: + images.append(page) + images.append(page) + + return images + + +def subtitle(request, resource): + """Return the subtitle string of the resource. + + Used after the resource name in the filename, and + also on the resource image. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + text for subtitle (str). + """ + return "{} - {}".format( + retrieve_query_parameter(request, "type").replace("_", " "), + retrieve_query_parameter(request, "paper_size") + ) + + +def valid_options(): + """Provide dictionary of all valid parameters. + + This excludes the header text parameter. + + Returns: + All valid options (dict). + """ + return { + "type": [ + "small_numbers", "large_numbers", "fractions", "maori_numbers", + "words", "letters", "maori_colours" + ], + "paper_size": ["a4", "letter"], + } diff --git a/csunplugged/resources/views/treasure_hunt.py b/csunplugged/resources/views/treasure_hunt.py new file mode 100644 index 000000000..ccb221147 --- /dev/null +++ b/csunplugged/resources/views/treasure_hunt.py @@ -0,0 +1,133 @@ +"""Module for generating Treasure Hunt resource.""" + +from PIL import Image, ImageDraw, ImageFont +from random import sample +from utils.retrieve_query_parameter import retrieve_query_parameter + + +def resource_image(request, resource): + """Create a image for Treasure Hunt resource. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + A Pillow image object. + """ + image_path = "static/img/resources/resource-treasure-hunt.png" + font_path = "static/fonts/PatrickHand-Regular.ttf" + image = Image.open(image_path) + draw = ImageDraw.Draw(image) + + # Add numbers to image if required + parameter_options = valid_options() + prefilled_values = retrieve_query_parameter(request, "prefilled_values", parameter_options["prefilled_values"]) + number_order = retrieve_query_parameter(request, "number_order", parameter_options["number_order"]) + + if prefilled_values != "blank": + (range_min, range_max, font_size) = number_range(request) + font = ImageFont.truetype(font_path, font_size) + + total_numbers = 26 + numbers = sample(range(range_min, range_max), total_numbers) + if number_order == "sorted": + numbers.sort() + + starting_coord_y = 494 + base_coord_y = starting_coord_y + coord_y_increment = 286 + base_coords_x = [257, 692] + for i in range(0, total_numbers): + text = str(numbers[i]) + text_width, text_height = draw.textsize(text, font=font) + + coord_x = base_coords_x[i % 2] - (text_width / 2) + coord_y = base_coord_y - (text_height / 2) + if i % 2 == 1: + coord_y -= 10 + base_coord_y += coord_y_increment + draw.text( + (coord_x, coord_y), + text, + font=font, + fill="#000" + ) + + # Add number order and range text + text = subtitle(request, resource) + font = ImageFont.truetype(font_path, 110) + text_width, text_height = draw.textsize(text, font=font) + coord_x = 1472 - (text_width / 2) + coord_y = 35 - (text_height / 2) + draw.text( + (coord_x, coord_y), + text, + font=font, + fill="#000" + ) + + return image + + +def subtitle(request, resource): + """Return the subtitle string of the resource. + + Used after the resource name in the filename, and + also on the resource image. + + Args: + request: HTTP request object (HttpRequest). + resource: Object of resource data (Resource). + + Returns: + text for subtitle (str) + """ + prefilled_values = retrieve_query_parameter(request, "prefilled_values") + if prefilled_values == "blank": + text = "blank" + else: + SUBTITLE_TEMPLATE = "{} - {} to {}" + number_order_text = retrieve_query_parameter(request, "number_order").title() + range_min, range_max, font_size = number_range(request) + text = SUBTITLE_TEMPLATE.format(number_order_text, range_min, range_max - 1) + return "{} - {}".format(text, retrieve_query_parameter(request, "paper_size")) + + +def number_range(request): + """Return number range tuple for resource. + + Args: + request: HTTP request object (HttpRequest). + + Returns: + Tuple of (range_min, range_max, font_size) + """ + parameter_options = valid_options() + prefilled_values = retrieve_query_parameter(request, "prefilled_values", parameter_options["prefilled_values"]) + range_min = 0 + if prefilled_values == "easy": + range_max = 100 + font_size = 97 + elif prefilled_values == "medium": + range_max = 1000 + font_size = 80 + elif prefilled_values == "hard": + range_max = 10000 + font_size = 70 + return (range_min, range_max, font_size) + + +def valid_options(): + """Provide dictionary of all valid parameters. + + This excludes the header text parameter. + + Returns: + All valid options (dict). + """ + return { + "prefilled_values": ["blank", "easy", "medium", "hard"], + "number_order": ["sorted", "unsorted"], + "paper_size": ["a4", "letter"], + } diff --git a/csunplugged/resources/views/views.py b/csunplugged/resources/views/views.py new file mode 100644 index 000000000..6c695b9d7 --- /dev/null +++ b/csunplugged/resources/views/views.py @@ -0,0 +1,94 @@ +"""Views for the resource application.""" + +from django.conf import settings +from django.views import generic +from django.shortcuts import get_object_or_404, render, redirect +from django.http import Http404, HttpResponse +from resources.models import Resource +from .generate_resource_pdf import generate_resource_pdf +import importlib +from utils.group_lessons_by_age import group_lessons_by_age + +RESPONSE_CONTENT_DISPOSITION = 'attachment; filename="{filename}.pdf"' + + +class IndexView(generic.ListView): + """View for the resource application homepage.""" + + template_name = "resources/index.html" + context_object_name = "all_resources" + + def get_queryset(self): + """Get queryset of all resources. + + Returns: + Queryset of all resources ordered by name. + """ + return Resource.objects.order_by("name") + + +def resource(request, resource_slug): + """View for a specific resource in the resources application. + + Returns: + HTML response of webpage, 404 if not found. + """ + resource = get_object_or_404(Resource, slug=resource_slug) + context = dict() + context["resource"] = resource + context["debug"] = settings.DEBUG + context["grouped_lessons"] = group_lessons_by_age(resource.lessons.all()) + if resource.thumbnail_static_path: + context["thumbnail"] = resource.thumbnail_static_path + return render(request, resource.webpage_template, context) + + +def generate_resource(request, resource_slug): + """View for generated PDF of a specific resource. + + Returns: + HTML response containing PDF of resource, 404 if not found. + """ + resource = get_object_or_404(Resource, slug=resource_slug) + resource_view = resource.generation_view + # Remove .py extension if given + # TODO: Move logic to loaders + if resource_view.endswith(".py"): + resource_view = resource_view[:-3] + module_path = "resources.views.{}".format(resource_view) + spec = importlib.util.find_spec(module_path) + if spec is None: + raise Http404("PDF generation does not exist for resource: {}".format(resource_slug)) + else: + # TODO: Weasyprint handling in production + # TODO: Add creation of PDF as job to job queue + import environ + env = environ.Env( + DJANGO_PRODUCTION=(bool), + ) + if env("DJANGO_PRODUCTION"): + # Return cached static PDF file of resource + return resource_pdf_cache(request, resource, module_path) + else: + (pdf_file, filename) = generate_resource_pdf(request, resource, module_path) + response = HttpResponse(pdf_file, content_type="application/pdf") + response["Content-Disposition"] = RESPONSE_CONTENT_DISPOSITION.format(filename=filename) + return response + + +def resource_pdf_cache(request, resource, module_path): + """Provide redirect to static resource file. + + Args: + request: HttpRequest object. + resource: Resource model object. + module_path: Path to resource module (str). + + Returns: + HTTP redirect. + """ + resource_image_generator = importlib.import_module(module_path) + subtitle = resource_image_generator.subtitle(request.GET, resource) + filename = "{} ({})".format(resource.name, subtitle) + redirect_url = "{}resources/{}.pdf".format(settings.STATIC_URL, filename) + return redirect(redirect_url) diff --git a/csunplugged/static/fonts/NotoSans-Regular-LICENSE.txt b/csunplugged/static/fonts/NotoSans-Regular-LICENSE.txt new file mode 100644 index 000000000..d952d62c0 --- /dev/null +++ b/csunplugged/static/fonts/NotoSans-Regular-LICENSE.txt @@ -0,0 +1,92 @@ +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/csunplugged/static/fonts/NotoSans-Regular.ttf b/csunplugged/static/fonts/NotoSans-Regular.ttf new file mode 100644 index 000000000..04be6f5ee Binary files /dev/null and b/csunplugged/static/fonts/NotoSans-Regular.ttf differ diff --git a/csunplugged/static/fonts/PatrickHand-LICENSE.md b/csunplugged/static/fonts/PatrickHand-LICENSE.md new file mode 100644 index 000000000..870789c29 --- /dev/null +++ b/csunplugged/static/fonts/PatrickHand-LICENSE.md @@ -0,0 +1,92 @@ +Copyright (c) 2010-2012 Patrick Wagesreiter (mail@patrickwagesreiter.at) +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/csunplugged/static/fonts/PatrickHand-Regular.ttf b/csunplugged/static/fonts/PatrickHand-Regular.ttf new file mode 100644 index 000000000..fb45ccdbd Binary files /dev/null and b/csunplugged/static/fonts/PatrickHand-Regular.ttf differ diff --git a/csunplugged/static/img/android-chrome-192x192.png b/csunplugged/static/img/android-chrome-192x192.png new file mode 100644 index 000000000..d476f7561 Binary files /dev/null and b/csunplugged/static/img/android-chrome-192x192.png differ diff --git a/csunplugged/static/img/android-chrome-256x256.png b/csunplugged/static/img/android-chrome-256x256.png new file mode 100644 index 000000000..c80622108 Binary files /dev/null and b/csunplugged/static/img/android-chrome-256x256.png differ diff --git a/csunplugged/static/img/apple-touch-icon.png b/csunplugged/static/img/apple-touch-icon.png new file mode 100644 index 000000000..c1003ef75 Binary files /dev/null and b/csunplugged/static/img/apple-touch-icon.png differ diff --git a/csunplugged/static/img/browserconfig.xml b/csunplugged/static/img/browserconfig.xml new file mode 100644 index 000000000..238951fa8 --- /dev/null +++ b/csunplugged/static/img/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #db5a35 + + + diff --git a/csunplugged/static/img/favicon-16x16.png b/csunplugged/static/img/favicon-16x16.png new file mode 100644 index 000000000..5d770e59a Binary files /dev/null and b/csunplugged/static/img/favicon-16x16.png differ diff --git a/csunplugged/static/img/favicon-32x32.png b/csunplugged/static/img/favicon-32x32.png new file mode 100644 index 000000000..4f89aa2ca Binary files /dev/null and b/csunplugged/static/img/favicon-32x32.png differ diff --git a/csunplugged/static/img/favicon.ico b/csunplugged/static/img/favicon.ico new file mode 100644 index 000000000..7e47b7586 Binary files /dev/null and b/csunplugged/static/img/favicon.ico differ diff --git a/csunplugged/static/img/general/binary-cards-5.png b/csunplugged/static/img/general/binary-cards-5.png new file mode 100644 index 000000000..2d8bb0a57 Binary files /dev/null and b/csunplugged/static/img/general/binary-cards-5.png differ diff --git a/csunplugged/static/img/general/computer-searching.png b/csunplugged/static/img/general/computer-searching.png new file mode 100644 index 000000000..8934276cb Binary files /dev/null and b/csunplugged/static/img/general/computer-searching.png differ diff --git a/csunplugged/static/img/general/ct-abstraction.png b/csunplugged/static/img/general/ct-abstraction.png new file mode 100644 index 000000000..497f48ba6 Binary files /dev/null and b/csunplugged/static/img/general/ct-abstraction.png differ diff --git a/csunplugged/static/img/general/ct-algorithm.png b/csunplugged/static/img/general/ct-algorithm.png new file mode 100644 index 000000000..b56b18686 Binary files /dev/null and b/csunplugged/static/img/general/ct-algorithm.png differ diff --git a/csunplugged/static/img/general/ct-decomposition.png b/csunplugged/static/img/general/ct-decomposition.png new file mode 100644 index 000000000..d3c97d704 Binary files /dev/null and b/csunplugged/static/img/general/ct-decomposition.png differ diff --git a/csunplugged/static/img/general/ct-evaluation.png b/csunplugged/static/img/general/ct-evaluation.png new file mode 100644 index 000000000..ec4a41d6a Binary files /dev/null and b/csunplugged/static/img/general/ct-evaluation.png differ diff --git a/csunplugged/static/img/general/ct-logic.png b/csunplugged/static/img/general/ct-logic.png new file mode 100644 index 000000000..ed25e399b Binary files /dev/null and b/csunplugged/static/img/general/ct-logic.png differ diff --git a/csunplugged/static/img/general/ct-pattern.png b/csunplugged/static/img/general/ct-pattern.png new file mode 100644 index 000000000..696b3608f Binary files /dev/null and b/csunplugged/static/img/general/ct-pattern.png differ diff --git a/csunplugged/static/img/general/detective.png b/csunplugged/static/img/general/detective.png new file mode 100644 index 000000000..b646b5e2c Binary files /dev/null and b/csunplugged/static/img/general/detective.png differ diff --git a/csunplugged/static/img/google-logo-colour.png b/csunplugged/static/img/google-logo-colour.png new file mode 100644 index 000000000..de8d297f8 Binary files /dev/null and b/csunplugged/static/img/google-logo-colour.png differ diff --git a/csunplugged/static/img/home-page-banner-photo.jpg b/csunplugged/static/img/home-page-banner-photo.jpg new file mode 100644 index 000000000..b0c7cb8c5 Binary files /dev/null and b/csunplugged/static/img/home-page-banner-photo.jpg differ diff --git a/csunplugged/static/img/logo-small.png b/csunplugged/static/img/logo-small.png new file mode 100644 index 000000000..7bf993a4f Binary files /dev/null and b/csunplugged/static/img/logo-small.png differ diff --git a/csunplugged/static/img/logo.png b/csunplugged/static/img/logo.png new file mode 100644 index 000000000..f59642fcd Binary files /dev/null and b/csunplugged/static/img/logo.png differ diff --git a/csunplugged/static/img/manifest.json b/csunplugged/static/img/manifest.json new file mode 100644 index 000000000..ec9fc4071 --- /dev/null +++ b/csunplugged/static/img/manifest.json @@ -0,0 +1,18 @@ +{ + "name": "", + "icons": [ + { + "src": "https://storage.googleapis.com/cs-unplugged-develop/static/img/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "https://storage.googleapis.com/cs-unplugged-develop/static/img/android-chrome-256x256.png", + "sizes": "256x256", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/csunplugged/static/img/microsoft-philanthropies-logo.png b/csunplugged/static/img/microsoft-philanthropies-logo.png new file mode 100644 index 000000000..67a9b7715 Binary files /dev/null and b/csunplugged/static/img/microsoft-philanthropies-logo.png differ diff --git a/csunplugged/static/img/mstile-150x150.png b/csunplugged/static/img/mstile-150x150.png new file mode 100644 index 000000000..856dc4a82 Binary files /dev/null and b/csunplugged/static/img/mstile-150x150.png differ diff --git a/csunplugged/static/img/python-logo.png b/csunplugged/static/img/python-logo.png new file mode 100644 index 000000000..b548e1d42 Binary files /dev/null and b/csunplugged/static/img/python-logo.png differ diff --git a/csunplugged/static/img/resources/binary-cards-small/binary-cards-small-1.png b/csunplugged/static/img/resources/binary-cards-small/binary-cards-small-1.png new file mode 100644 index 000000000..979b13197 Binary files /dev/null and b/csunplugged/static/img/resources/binary-cards-small/binary-cards-small-1.png differ diff --git a/csunplugged/static/img/resources/binary-cards-small/binary-cards-small-2.png b/csunplugged/static/img/resources/binary-cards-small/binary-cards-small-2.png new file mode 100644 index 000000000..024f7c814 Binary files /dev/null and b/csunplugged/static/img/resources/binary-cards-small/binary-cards-small-2.png differ diff --git a/csunplugged/static/img/resources/binary-cards-small/binary-cards-small-3.png b/csunplugged/static/img/resources/binary-cards-small/binary-cards-small-3.png new file mode 100644 index 000000000..082d9d19c Binary files /dev/null and b/csunplugged/static/img/resources/binary-cards-small/binary-cards-small-3.png differ diff --git a/csunplugged/static/img/resources/binary-cards-small/thumbnail.png b/csunplugged/static/img/resources/binary-cards-small/thumbnail.png new file mode 100644 index 000000000..58e7f6cea Binary files /dev/null and b/csunplugged/static/img/resources/binary-cards-small/thumbnail.png differ diff --git a/csunplugged/static/img/resources/binary-cards/binary-cards-1-dot.png b/csunplugged/static/img/resources/binary-cards/binary-cards-1-dot.png new file mode 100644 index 000000000..65b4e7433 Binary files /dev/null and b/csunplugged/static/img/resources/binary-cards/binary-cards-1-dot.png differ diff --git a/csunplugged/static/img/resources/binary-cards/binary-cards-128-dots.png b/csunplugged/static/img/resources/binary-cards/binary-cards-128-dots.png new file mode 100644 index 000000000..2a8b3d8f3 Binary files /dev/null and b/csunplugged/static/img/resources/binary-cards/binary-cards-128-dots.png differ diff --git a/csunplugged/static/img/resources/binary-cards/binary-cards-16-dots.png b/csunplugged/static/img/resources/binary-cards/binary-cards-16-dots.png new file mode 100644 index 000000000..c26130937 Binary files /dev/null and b/csunplugged/static/img/resources/binary-cards/binary-cards-16-dots.png differ diff --git a/csunplugged/static/img/resources/binary-cards/binary-cards-2-dots.png b/csunplugged/static/img/resources/binary-cards/binary-cards-2-dots.png new file mode 100644 index 000000000..ad15d7ac1 Binary files /dev/null and b/csunplugged/static/img/resources/binary-cards/binary-cards-2-dots.png differ diff --git a/csunplugged/static/img/resources/binary-cards/binary-cards-32-dots.png b/csunplugged/static/img/resources/binary-cards/binary-cards-32-dots.png new file mode 100644 index 000000000..5a740ba78 Binary files /dev/null and b/csunplugged/static/img/resources/binary-cards/binary-cards-32-dots.png differ diff --git a/csunplugged/static/img/resources/binary-cards/binary-cards-4-dots.png b/csunplugged/static/img/resources/binary-cards/binary-cards-4-dots.png new file mode 100644 index 000000000..f95c2733f Binary files /dev/null and b/csunplugged/static/img/resources/binary-cards/binary-cards-4-dots.png differ diff --git a/csunplugged/static/img/resources/binary-cards/binary-cards-64-dots.png b/csunplugged/static/img/resources/binary-cards/binary-cards-64-dots.png new file mode 100644 index 000000000..c1244ec2d Binary files /dev/null and b/csunplugged/static/img/resources/binary-cards/binary-cards-64-dots.png differ diff --git a/csunplugged/static/img/resources/binary-cards/binary-cards-8-dots.png b/csunplugged/static/img/resources/binary-cards/binary-cards-8-dots.png new file mode 100644 index 000000000..6f22e26fa Binary files /dev/null and b/csunplugged/static/img/resources/binary-cards/binary-cards-8-dots.png differ diff --git a/csunplugged/static/img/resources/binary-cards/thumbnail.png b/csunplugged/static/img/resources/binary-cards/thumbnail.png new file mode 100644 index 000000000..f260e486a Binary files /dev/null and b/csunplugged/static/img/resources/binary-cards/thumbnail.png differ diff --git a/csunplugged/static/img/resources/binary-to-alphabet/table-teacher.png b/csunplugged/static/img/resources/binary-to-alphabet/table-teacher.png new file mode 100644 index 000000000..a7fec7742 Binary files /dev/null and b/csunplugged/static/img/resources/binary-to-alphabet/table-teacher.png differ diff --git a/csunplugged/static/img/resources/binary-to-alphabet/table.png b/csunplugged/static/img/resources/binary-to-alphabet/table.png new file mode 100644 index 000000000..7d1f6c2dc Binary files /dev/null and b/csunplugged/static/img/resources/binary-to-alphabet/table.png differ diff --git a/csunplugged/static/img/resources/binary-to-alphabet/thumbnail.png b/csunplugged/static/img/resources/binary-to-alphabet/thumbnail.png new file mode 100644 index 000000000..2b435e547 Binary files /dev/null and b/csunplugged/static/img/resources/binary-to-alphabet/thumbnail.png differ diff --git a/csunplugged/static/img/resources/binary-windows/binary-windows-1-to-8.png b/csunplugged/static/img/resources/binary-windows/binary-windows-1-to-8.png new file mode 100644 index 000000000..96738e677 Binary files /dev/null and b/csunplugged/static/img/resources/binary-windows/binary-windows-1-to-8.png differ diff --git a/csunplugged/static/img/resources/binary-windows/binary-windows-16-to-128.png b/csunplugged/static/img/resources/binary-windows/binary-windows-16-to-128.png new file mode 100644 index 000000000..935fe9b55 Binary files /dev/null and b/csunplugged/static/img/resources/binary-windows/binary-windows-16-to-128.png differ diff --git a/csunplugged/static/img/resources/binary-windows/binary-windows-blank.png b/csunplugged/static/img/resources/binary-windows/binary-windows-blank.png new file mode 100644 index 000000000..c32e75c71 Binary files /dev/null and b/csunplugged/static/img/resources/binary-windows/binary-windows-blank.png differ diff --git a/csunplugged/static/img/resources/binary-windows/thumbnail.png b/csunplugged/static/img/resources/binary-windows/thumbnail.png new file mode 100644 index 000000000..a29b8eca2 Binary files /dev/null and b/csunplugged/static/img/resources/binary-windows/thumbnail.png differ diff --git a/csunplugged/static/img/resources/modulo-clock/modulo-clock-10.png b/csunplugged/static/img/resources/modulo-clock/modulo-clock-10.png new file mode 100644 index 000000000..56f34908f Binary files /dev/null and b/csunplugged/static/img/resources/modulo-clock/modulo-clock-10.png differ diff --git a/csunplugged/static/img/resources/modulo-clock/modulo-clock-2.png b/csunplugged/static/img/resources/modulo-clock/modulo-clock-2.png new file mode 100644 index 000000000..ba0e2a1c9 Binary files /dev/null and b/csunplugged/static/img/resources/modulo-clock/modulo-clock-2.png differ diff --git a/csunplugged/static/img/resources/modulo-clock/thumbnail.png b/csunplugged/static/img/resources/modulo-clock/thumbnail.png new file mode 100644 index 000000000..1a5fd26cc Binary files /dev/null and b/csunplugged/static/img/resources/modulo-clock/thumbnail.png differ diff --git a/csunplugged/static/img/resources/parity-cards/thumbnail.png b/csunplugged/static/img/resources/parity-cards/thumbnail.png new file mode 100644 index 000000000..dd5f864ef Binary files /dev/null and b/csunplugged/static/img/resources/parity-cards/thumbnail.png differ diff --git a/csunplugged/static/img/resources/resource-sorting-network-colour.png b/csunplugged/static/img/resources/resource-sorting-network-colour.png new file mode 100644 index 000000000..68858168f Binary files /dev/null and b/csunplugged/static/img/resources/resource-sorting-network-colour.png differ diff --git a/csunplugged/static/img/resources/resource-sorting-network-thumbnail-example.png b/csunplugged/static/img/resources/resource-sorting-network-thumbnail-example.png new file mode 100644 index 000000000..4fe6b1c60 Binary files /dev/null and b/csunplugged/static/img/resources/resource-sorting-network-thumbnail-example.png differ diff --git a/csunplugged/static/img/resources/resource-treasure-hunt-thumbnail-example.png b/csunplugged/static/img/resources/resource-treasure-hunt-thumbnail-example.png new file mode 100644 index 000000000..a058b07c7 Binary files /dev/null and b/csunplugged/static/img/resources/resource-treasure-hunt-thumbnail-example.png differ diff --git a/csunplugged/static/img/resources/resource-treasure-hunt.png b/csunplugged/static/img/resources/resource-treasure-hunt.png new file mode 100644 index 000000000..347c68534 Binary files /dev/null and b/csunplugged/static/img/resources/resource-treasure-hunt.png differ diff --git a/csunplugged/static/img/resources/sorting-network-cards/thumbnail.png b/csunplugged/static/img/resources/sorting-network-cards/thumbnail.png new file mode 100644 index 000000000..61ee83592 Binary files /dev/null and b/csunplugged/static/img/resources/sorting-network-cards/thumbnail.png differ diff --git a/csunplugged/static/img/safari-pinned-tab.svg b/csunplugged/static/img/safari-pinned-tab.svg new file mode 100644 index 000000000..37a426972 --- /dev/null +++ b/csunplugged/static/img/safari-pinned-tab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/csunplugged/static/img/scratch-cat.png b/csunplugged/static/img/scratch-cat.png new file mode 100644 index 000000000..e6dfe0ffa Binary files /dev/null and b/csunplugged/static/img/scratch-cat.png differ diff --git a/csunplugged/static/img/topics/arrest-mugshot-for-typo.png b/csunplugged/static/img/topics/arrest-mugshot-for-typo.png new file mode 100644 index 000000000..09edd7691 Binary files /dev/null and b/csunplugged/static/img/topics/arrest-mugshot-for-typo.png differ diff --git a/csunplugged/static/img/topics/barcode-12-step-1.png b/csunplugged/static/img/topics/barcode-12-step-1.png new file mode 100644 index 000000000..63963af9a Binary files /dev/null and b/csunplugged/static/img/topics/barcode-12-step-1.png differ diff --git a/csunplugged/static/img/topics/barcode-12-step-2.png b/csunplugged/static/img/topics/barcode-12-step-2.png new file mode 100644 index 000000000..c6b31054a Binary files /dev/null and b/csunplugged/static/img/topics/barcode-12-step-2.png differ diff --git a/csunplugged/static/img/topics/barcode-12-step-3.png b/csunplugged/static/img/topics/barcode-12-step-3.png new file mode 100644 index 000000000..65a633f8e Binary files /dev/null and b/csunplugged/static/img/topics/barcode-12-step-3.png differ diff --git a/csunplugged/static/img/topics/barcode-12-step-4.png b/csunplugged/static/img/topics/barcode-12-step-4.png new file mode 100644 index 000000000..65ec929a3 Binary files /dev/null and b/csunplugged/static/img/topics/barcode-12-step-4.png differ diff --git a/csunplugged/static/img/topics/barcode-12-step-5.png b/csunplugged/static/img/topics/barcode-12-step-5.png new file mode 100644 index 000000000..a20ecaa11 Binary files /dev/null and b/csunplugged/static/img/topics/barcode-12-step-5.png differ diff --git a/csunplugged/static/img/topics/barcode-12-step-6.png b/csunplugged/static/img/topics/barcode-12-step-6.png new file mode 100644 index 000000000..c9a916538 Binary files /dev/null and b/csunplugged/static/img/topics/barcode-12-step-6.png differ diff --git a/csunplugged/static/img/topics/barcode-12-step-7.png b/csunplugged/static/img/topics/barcode-12-step-7.png new file mode 100644 index 000000000..1d191b896 Binary files /dev/null and b/csunplugged/static/img/topics/barcode-12-step-7.png differ diff --git a/csunplugged/static/img/topics/barcode-12.jpg b/csunplugged/static/img/topics/barcode-12.jpg new file mode 100644 index 000000000..b43f9deae Binary files /dev/null and b/csunplugged/static/img/topics/barcode-12.jpg differ diff --git a/csunplugged/static/img/topics/barcode-13-step-1.png b/csunplugged/static/img/topics/barcode-13-step-1.png new file mode 100644 index 000000000..3ee6c0b7f Binary files /dev/null and b/csunplugged/static/img/topics/barcode-13-step-1.png differ diff --git a/csunplugged/static/img/topics/barcode-13-step-2.png b/csunplugged/static/img/topics/barcode-13-step-2.png new file mode 100644 index 000000000..e0ca02b2a Binary files /dev/null and b/csunplugged/static/img/topics/barcode-13-step-2.png differ diff --git a/csunplugged/static/img/topics/barcode-13-step-3.png b/csunplugged/static/img/topics/barcode-13-step-3.png new file mode 100644 index 000000000..ff0d720d0 Binary files /dev/null and b/csunplugged/static/img/topics/barcode-13-step-3.png differ diff --git a/csunplugged/static/img/topics/barcode-13-step-4.png b/csunplugged/static/img/topics/barcode-13-step-4.png new file mode 100644 index 000000000..471816fc1 Binary files /dev/null and b/csunplugged/static/img/topics/barcode-13-step-4.png differ diff --git a/csunplugged/static/img/topics/barcode-13-step-5.png b/csunplugged/static/img/topics/barcode-13-step-5.png new file mode 100644 index 000000000..c3b03ca78 Binary files /dev/null and b/csunplugged/static/img/topics/barcode-13-step-5.png differ diff --git a/csunplugged/static/img/topics/barcode-13-step-6.png b/csunplugged/static/img/topics/barcode-13-step-6.png new file mode 100644 index 000000000..f66fa2d09 Binary files /dev/null and b/csunplugged/static/img/topics/barcode-13-step-6.png differ diff --git a/csunplugged/static/img/topics/barcode-13-step-7.png b/csunplugged/static/img/topics/barcode-13-step-7.png new file mode 100644 index 000000000..a0a9bec04 Binary files /dev/null and b/csunplugged/static/img/topics/barcode-13-step-7.png differ diff --git a/csunplugged/static/img/topics/barcode-13.jpg b/csunplugged/static/img/topics/barcode-13.jpg new file mode 100644 index 000000000..49fa9df4c Binary files /dev/null and b/csunplugged/static/img/topics/barcode-13.jpg differ diff --git a/csunplugged/static/img/topics/binary-cards-4-bits-icon.png b/csunplugged/static/img/topics/binary-cards-4-bits-icon.png new file mode 100644 index 000000000..c08089588 Binary files /dev/null and b/csunplugged/static/img/topics/binary-cards-4-bits-icon.png differ diff --git a/csunplugged/static/img/topics/binary-cards-total-9.png b/csunplugged/static/img/topics/binary-cards-total-9.png new file mode 100644 index 000000000..173761475 Binary files /dev/null and b/csunplugged/static/img/topics/binary-cards-total-9.png differ diff --git a/csunplugged/static/img/topics/binary-numbers-0-1.png b/csunplugged/static/img/topics/binary-numbers-0-1.png new file mode 100644 index 000000000..857ad3229 Binary files /dev/null and b/csunplugged/static/img/topics/binary-numbers-0-1.png differ diff --git a/csunplugged/static/img/topics/binary-picture-showing-bits.png b/csunplugged/static/img/topics/binary-picture-showing-bits.png new file mode 100644 index 000000000..2d3df097b Binary files /dev/null and b/csunplugged/static/img/topics/binary-picture-showing-bits.png differ diff --git a/csunplugged/static/img/topics/binary_cards.png b/csunplugged/static/img/topics/binary_cards.png new file mode 100644 index 000000000..30140f484 Binary files /dev/null and b/csunplugged/static/img/topics/binary_cards.png differ diff --git a/csunplugged/static/img/topics/binary_cards_equals_three.png b/csunplugged/static/img/topics/binary_cards_equals_three.png new file mode 100644 index 000000000..fc55baeaa Binary files /dev/null and b/csunplugged/static/img/topics/binary_cards_equals_three.png differ diff --git a/csunplugged/static/img/topics/binary_count_girl.png b/csunplugged/static/img/topics/binary_count_girl.png new file mode 100644 index 000000000..209977f6a Binary files /dev/null and b/csunplugged/static/img/topics/binary_count_girl.png differ diff --git a/csunplugged/static/img/topics/binary_dna.png b/csunplugged/static/img/topics/binary_dna.png new file mode 100644 index 000000000..8a2e2547b Binary files /dev/null and b/csunplugged/static/img/topics/binary_dna.png differ diff --git a/csunplugged/static/img/topics/binary_grids.png b/csunplugged/static/img/topics/binary_grids.png new file mode 100644 index 000000000..afa5b8f14 Binary files /dev/null and b/csunplugged/static/img/topics/binary_grids.png differ diff --git a/csunplugged/static/img/topics/binary_torch.png b/csunplugged/static/img/topics/binary_torch.png new file mode 100644 index 000000000..1364aee8b Binary files /dev/null and b/csunplugged/static/img/topics/binary_torch.png differ diff --git a/csunplugged/static/img/topics/cd-with-marks.png b/csunplugged/static/img/topics/cd-with-marks.png new file mode 100644 index 000000000..a28f1660f Binary files /dev/null and b/csunplugged/static/img/topics/cd-with-marks.png differ diff --git a/csunplugged/static/img/topics/col_binary_0.png b/csunplugged/static/img/topics/col_binary_0.png new file mode 100644 index 000000000..9e740d204 Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_0.png differ diff --git a/csunplugged/static/img/topics/col_binary_1.png b/csunplugged/static/img/topics/col_binary_1.png new file mode 100644 index 000000000..ec4d21c22 Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_1.png differ diff --git a/csunplugged/static/img/topics/col_binary_2cards.png b/csunplugged/static/img/topics/col_binary_2cards.png new file mode 100644 index 000000000..a2d986a42 Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_2cards.png differ diff --git a/csunplugged/static/img/topics/col_binary_3cards.png b/csunplugged/static/img/topics/col_binary_3cards.png new file mode 100644 index 000000000..9a7edc625 Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_3cards.png differ diff --git a/csunplugged/static/img/topics/col_binary_4_kids_2_cards.png b/csunplugged/static/img/topics/col_binary_4_kids_2_cards.png new file mode 100644 index 000000000..f28b6aada Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_4_kids_2_cards.png differ diff --git a/csunplugged/static/img/topics/col_binary_4_kids_3_cards.png b/csunplugged/static/img/topics/col_binary_4_kids_3_cards.png new file mode 100644 index 000000000..d8cc51ae9 Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_4_kids_3_cards.png differ diff --git a/csunplugged/static/img/topics/col_binary_4_kids_4_cards.png b/csunplugged/static/img/topics/col_binary_4_kids_4_cards.png new file mode 100644 index 000000000..35282ea5b Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_4_kids_4_cards.png differ diff --git a/csunplugged/static/img/topics/col_binary_5cards.png b/csunplugged/static/img/topics/col_binary_5cards.png new file mode 100644 index 000000000..4b7bb8d5f Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_5cards.png differ diff --git a/csunplugged/static/img/topics/col_binary_CSU_boy_hands.png b/csunplugged/static/img/topics/col_binary_CSU_boy_hands.png new file mode 100644 index 000000000..53abcf310 Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_CSU_boy_hands.png differ diff --git a/csunplugged/static/img/topics/col_binary_apple.png b/csunplugged/static/img/topics/col_binary_apple.png new file mode 100644 index 000000000..c990e0867 Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_apple.png differ diff --git a/csunplugged/static/img/topics/col_binary_birthdayFinal.png b/csunplugged/static/img/topics/col_binary_birthdayFinal.png new file mode 100644 index 000000000..712640e7c Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_birthdayFinal.png differ diff --git a/csunplugged/static/img/topics/col_binary_bite_vs_byte.png b/csunplugged/static/img/topics/col_binary_bite_vs_byte.png new file mode 100644 index 000000000..ac01ff3fe Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_bite_vs_byte.png differ diff --git a/csunplugged/static/img/topics/col_binary_cake.png b/csunplugged/static/img/topics/col_binary_cake.png new file mode 100644 index 000000000..52e38491e Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_cake.png differ diff --git a/csunplugged/static/img/topics/col_binary_counting_pattern.png b/csunplugged/static/img/topics/col_binary_counting_pattern.png new file mode 100644 index 000000000..04346f18d Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_counting_pattern.png differ diff --git a/csunplugged/static/img/topics/col_binary_csu_girl_hands.png b/csunplugged/static/img/topics/col_binary_csu_girl_hands.png new file mode 100644 index 000000000..c851bb322 Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_csu_girl_hands.png differ diff --git a/csunplugged/static/img/topics/col_binary_lightbulb.png b/csunplugged/static/img/topics/col_binary_lightbulb.png new file mode 100644 index 000000000..16e076884 Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_lightbulb.png differ diff --git a/csunplugged/static/img/topics/col_binary_lightbulb_off.png b/csunplugged/static/img/topics/col_binary_lightbulb_off.png new file mode 100644 index 000000000..d8139f25c Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_lightbulb_off.png differ diff --git a/csunplugged/static/img/topics/col_binary_necklace_copy.png b/csunplugged/static/img/topics/col_binary_necklace_copy.png new file mode 100644 index 000000000..64f6b3996 Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_necklace_copy.png differ diff --git a/csunplugged/static/img/topics/col_binary_robot_boy_convo.png b/csunplugged/static/img/topics/col_binary_robot_boy_convo.png new file mode 100644 index 000000000..93d831d1e Binary files /dev/null and b/csunplugged/static/img/topics/col_binary_robot_boy_convo.png differ diff --git a/csunplugged/static/img/topics/computer-error.png b/csunplugged/static/img/topics/computer-error.png new file mode 100644 index 000000000..db0083aef Binary files /dev/null and b/csunplugged/static/img/topics/computer-error.png differ diff --git a/csunplugged/static/img/topics/error-correction-paint-tin.png b/csunplugged/static/img/topics/error-correction-paint-tin.png new file mode 100644 index 000000000..e48a29bc5 Binary files /dev/null and b/csunplugged/static/img/topics/error-correction-paint-tin.png differ diff --git a/csunplugged/static/img/topics/gobbly-gook-computer.png b/csunplugged/static/img/topics/gobbly-gook-computer.png new file mode 100644 index 000000000..dc2f3c48e Binary files /dev/null and b/csunplugged/static/img/topics/gobbly-gook-computer.png differ diff --git a/csunplugged/static/img/topics/kids-parity-trick.png b/csunplugged/static/img/topics/kids-parity-trick.png new file mode 100644 index 000000000..c6970eccb Binary files /dev/null and b/csunplugged/static/img/topics/kids-parity-trick.png differ diff --git a/csunplugged/static/img/topics/lightbulb_series_1.png b/csunplugged/static/img/topics/lightbulb_series_1.png new file mode 100755 index 000000000..6a301601c Binary files /dev/null and b/csunplugged/static/img/topics/lightbulb_series_1.png differ diff --git a/csunplugged/static/img/topics/lightbulb_series_2.png b/csunplugged/static/img/topics/lightbulb_series_2.png new file mode 100755 index 000000000..beaf74975 Binary files /dev/null and b/csunplugged/static/img/topics/lightbulb_series_2.png differ diff --git a/csunplugged/static/img/topics/lightbulb_series_3.png b/csunplugged/static/img/topics/lightbulb_series_3.png new file mode 100755 index 000000000..2a1730a9e Binary files /dev/null and b/csunplugged/static/img/topics/lightbulb_series_3.png differ diff --git a/csunplugged/static/img/topics/lightbulb_series_4.png b/csunplugged/static/img/topics/lightbulb_series_4.png new file mode 100755 index 000000000..68876af68 Binary files /dev/null and b/csunplugged/static/img/topics/lightbulb_series_4.png differ diff --git a/csunplugged/static/img/topics/lightbulb_series_4_bulbs_2.png b/csunplugged/static/img/topics/lightbulb_series_4_bulbs_2.png new file mode 100644 index 000000000..552f3744c Binary files /dev/null and b/csunplugged/static/img/topics/lightbulb_series_4_bulbs_2.png differ diff --git a/csunplugged/static/img/topics/lightbulb_series_4_bulbs_3.png b/csunplugged/static/img/topics/lightbulb_series_4_bulbs_3.png new file mode 100644 index 000000000..1421f67b5 Binary files /dev/null and b/csunplugged/static/img/topics/lightbulb_series_4_bulbs_3.png differ diff --git a/csunplugged/static/img/topics/lightbulb_series_4_bulbs_4.png b/csunplugged/static/img/topics/lightbulb_series_4_bulbs_4.png new file mode 100644 index 000000000..269dd2c5f Binary files /dev/null and b/csunplugged/static/img/topics/lightbulb_series_4_bulbs_4.png differ diff --git a/csunplugged/static/img/topics/mug-with-barcode.png b/csunplugged/static/img/topics/mug-with-barcode.png new file mode 100644 index 000000000..ec256be93 Binary files /dev/null and b/csunplugged/static/img/topics/mug-with-barcode.png differ diff --git a/csunplugged/static/img/topics/odd-parroty-parity.png b/csunplugged/static/img/topics/odd-parroty-parity.png new file mode 100644 index 000000000..64399c5b6 Binary files /dev/null and b/csunplugged/static/img/topics/odd-parroty-parity.png differ diff --git a/csunplugged/static/img/topics/par-pair.png b/csunplugged/static/img/topics/par-pair.png new file mode 100644 index 000000000..cbdc7d379 Binary files /dev/null and b/csunplugged/static/img/topics/par-pair.png differ diff --git a/csunplugged/static/img/topics/parity-cards-6x6-grid-step-1.png b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-1.png new file mode 100644 index 000000000..87ff2e785 Binary files /dev/null and b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-1.png differ diff --git a/csunplugged/static/img/topics/parity-cards-6x6-grid-step-10.png b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-10.png new file mode 100644 index 000000000..d56e91558 Binary files /dev/null and b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-10.png differ diff --git a/csunplugged/static/img/topics/parity-cards-6x6-grid-step-11.png b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-11.png new file mode 100644 index 000000000..2271e76b3 Binary files /dev/null and b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-11.png differ diff --git a/csunplugged/static/img/topics/parity-cards-6x6-grid-step-12.png b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-12.png new file mode 100644 index 000000000..d45cc4631 Binary files /dev/null and b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-12.png differ diff --git a/csunplugged/static/img/topics/parity-cards-6x6-grid-step-2.png b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-2.png new file mode 100644 index 000000000..9b3782ec1 Binary files /dev/null and b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-2.png differ diff --git a/csunplugged/static/img/topics/parity-cards-6x6-grid-step-3.png b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-3.png new file mode 100644 index 000000000..dc11e10e0 Binary files /dev/null and b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-3.png differ diff --git a/csunplugged/static/img/topics/parity-cards-6x6-grid-step-4.png b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-4.png new file mode 100644 index 000000000..c98c0b20f Binary files /dev/null and b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-4.png differ diff --git a/csunplugged/static/img/topics/parity-cards-6x6-grid-step-5.png b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-5.png new file mode 100644 index 000000000..a34270c73 Binary files /dev/null and b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-5.png differ diff --git a/csunplugged/static/img/topics/parity-cards-6x6-grid-step-6.png b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-6.png new file mode 100644 index 000000000..95ff28236 Binary files /dev/null and b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-6.png differ diff --git a/csunplugged/static/img/topics/parity-cards-6x6-grid-step-7.png b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-7.png new file mode 100644 index 000000000..acaaac528 Binary files /dev/null and b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-7.png differ diff --git a/csunplugged/static/img/topics/parity-cards-6x6-grid-step-8.png b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-8.png new file mode 100644 index 000000000..c5cf9f32d Binary files /dev/null and b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-8.png differ diff --git a/csunplugged/static/img/topics/parity-cards-6x6-grid-step-9.png b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-9.png new file mode 100644 index 000000000..a9c62e2a3 Binary files /dev/null and b/csunplugged/static/img/topics/parity-cards-6x6-grid-step-9.png differ diff --git a/csunplugged/static/img/topics/parity-cards.png b/csunplugged/static/img/topics/parity-cards.png new file mode 100644 index 000000000..3b3e09347 Binary files /dev/null and b/csunplugged/static/img/topics/parity-cards.png differ diff --git a/csunplugged/static/img/topics/parity-trick-example-icon.png b/csunplugged/static/img/topics/parity-trick-example-icon.png new file mode 100644 index 000000000..5ed9397e6 Binary files /dev/null and b/csunplugged/static/img/topics/parity-trick-example-icon.png differ diff --git a/csunplugged/static/img/topics/parity-trick-example.png b/csunplugged/static/img/topics/parity-trick-example.png new file mode 100644 index 000000000..fd19ad5a9 Binary files /dev/null and b/csunplugged/static/img/topics/parity-trick-example.png differ diff --git a/csunplugged/static/img/topics/parity-wizard.png b/csunplugged/static/img/topics/parity-wizard.png new file mode 100644 index 000000000..614bd45a9 Binary files /dev/null and b/csunplugged/static/img/topics/parity-wizard.png differ diff --git a/csunplugged/static/img/topics/programming-challenges/binary-challenge-9-example1.png b/csunplugged/static/img/topics/programming-challenges/binary-challenge-9-example1.png new file mode 100644 index 000000000..26b743812 Binary files /dev/null and b/csunplugged/static/img/topics/programming-challenges/binary-challenge-9-example1.png differ diff --git a/csunplugged/static/img/topics/programming-challenges/binary-challenge-9-example2.png b/csunplugged/static/img/topics/programming-challenges/binary-challenge-9-example2.png new file mode 100644 index 000000000..2e6432e90 Binary files /dev/null and b/csunplugged/static/img/topics/programming-challenges/binary-challenge-9-example2.png differ diff --git a/csunplugged/static/img/topics/school-test-error.png b/csunplugged/static/img/topics/school-test-error.png new file mode 100644 index 000000000..48bed9460 Binary files /dev/null and b/csunplugged/static/img/topics/school-test-error.png differ diff --git a/csunplugged/static/img/topics/shannon-juggling.png b/csunplugged/static/img/topics/shannon-juggling.png new file mode 100644 index 000000000..8134cfc45 Binary files /dev/null and b/csunplugged/static/img/topics/shannon-juggling.png differ diff --git a/csunplugged/static/img/topics/sorting-network-ancient-sorting-network-text-en.png b/csunplugged/static/img/topics/sorting-network-ancient-sorting-network-text-en.png new file mode 100644 index 000000000..1c14645ff Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-ancient-sorting-network-text-en.png differ diff --git a/csunplugged/static/img/topics/sorting-network-comparing-apples.png b/csunplugged/static/img/topics/sorting-network-comparing-apples.png new file mode 100644 index 000000000..3b901d8db Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-comparing-apples.png differ diff --git a/csunplugged/static/img/topics/sorting-network-confused-people.png b/csunplugged/static/img/topics/sorting-network-confused-people.png new file mode 100644 index 000000000..0350cca2a Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-confused-people.png differ diff --git a/csunplugged/static/img/topics/sorting-network-crochet-v-crocodile.png b/csunplugged/static/img/topics/sorting-network-crochet-v-crocodile.png new file mode 100644 index 000000000..256016e73 Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-crochet-v-crocodile.png differ diff --git a/csunplugged/static/img/topics/sorting-network-digging-hole-text-en.png b/csunplugged/static/img/topics/sorting-network-digging-hole-text-en.png new file mode 100644 index 000000000..b881f9443 Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-digging-hole-text-en.png differ diff --git a/csunplugged/static/img/topics/sorting-network-equal-3.png b/csunplugged/static/img/topics/sorting-network-equal-3.png new file mode 100644 index 000000000..ef909ee0e Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-equal-3.png differ diff --git a/csunplugged/static/img/topics/sorting-network-example-cards-1.jpg b/csunplugged/static/img/topics/sorting-network-example-cards-1.jpg new file mode 100644 index 000000000..2ce7bc2c4 Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-example-cards-1.jpg differ diff --git a/csunplugged/static/img/topics/sorting-network-example-cards-2.jpg b/csunplugged/static/img/topics/sorting-network-example-cards-2.jpg new file mode 100644 index 000000000..c53354cb6 Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-example-cards-2.jpg differ diff --git a/csunplugged/static/img/topics/sorting-network-example-cards-3.jpg b/csunplugged/static/img/topics/sorting-network-example-cards-3.jpg new file mode 100644 index 000000000..8cbd579b2 Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-example-cards-3.jpg differ diff --git a/csunplugged/static/img/topics/sorting-network-kids.png b/csunplugged/static/img/topics/sorting-network-kids.png new file mode 100644 index 000000000..9e08a644d Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-kids.png differ diff --git a/csunplugged/static/img/topics/sorting-network-many-computers-vs-one.png b/csunplugged/static/img/topics/sorting-network-many-computers-vs-one.png new file mode 100644 index 000000000..69ad252ff Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-many-computers-vs-one.png differ diff --git a/csunplugged/static/img/topics/sorting-network-node-1.png b/csunplugged/static/img/topics/sorting-network-node-1.png new file mode 100644 index 000000000..54047552c Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-node-1.png differ diff --git a/csunplugged/static/img/topics/sorting-network-node-2.png b/csunplugged/static/img/topics/sorting-network-node-2.png new file mode 100644 index 000000000..574f64cf9 Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-node-2.png differ diff --git a/csunplugged/static/img/topics/sorting-network-office-note-text-en.png b/csunplugged/static/img/topics/sorting-network-office-note-text-en.png new file mode 100644 index 000000000..6fa1cf97b Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-office-note-text-en.png differ diff --git a/csunplugged/static/img/topics/sorting-network-sorted.png b/csunplugged/static/img/topics/sorting-network-sorted.png new file mode 100644 index 000000000..d42b9120e Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-sorted.png differ diff --git a/csunplugged/static/img/topics/sorting-network-toffees-cellos-sponge.png b/csunplugged/static/img/topics/sorting-network-toffees-cellos-sponge.png new file mode 100644 index 000000000..c0e22e89b Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-toffees-cellos-sponge.png differ diff --git a/csunplugged/static/img/topics/sorting-network-too-far-kid.png b/csunplugged/static/img/topics/sorting-network-too-far-kid.png new file mode 100644 index 000000000..ce9a0a9d3 Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-too-far-kid.png differ diff --git a/csunplugged/static/img/topics/sorting-network-tortoises-vs-rabbit.png b/csunplugged/static/img/topics/sorting-network-tortoises-vs-rabbit.png new file mode 100644 index 000000000..327864481 Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-tortoises-vs-rabbit.png differ diff --git a/csunplugged/static/img/topics/sorting-network-unsorted.png b/csunplugged/static/img/topics/sorting-network-unsorted.png new file mode 100644 index 000000000..0c9b06c63 Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-unsorted.png differ diff --git a/csunplugged/static/img/topics/sorting-network-variation-alphabet.png b/csunplugged/static/img/topics/sorting-network-variation-alphabet.png new file mode 100644 index 000000000..200a4e8b8 Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-variation-alphabet.png differ diff --git a/csunplugged/static/img/topics/sorting-network-variation-aural.png b/csunplugged/static/img/topics/sorting-network-variation-aural.png new file mode 100644 index 000000000..f7dc9bf04 Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-variation-aural.png differ diff --git a/csunplugged/static/img/topics/sorting-network-variation-music.png b/csunplugged/static/img/topics/sorting-network-variation-music.png new file mode 100644 index 000000000..8df8f8552 Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-variation-music.png differ diff --git a/csunplugged/static/img/topics/sorting-network-variation-words-2.png b/csunplugged/static/img/topics/sorting-network-variation-words-2.png new file mode 100644 index 000000000..8acb92bc6 Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-variation-words-2.png differ diff --git a/csunplugged/static/img/topics/sorting-network-variation-words.png b/csunplugged/static/img/topics/sorting-network-variation-words.png new file mode 100644 index 000000000..edbd36b9b Binary files /dev/null and b/csunplugged/static/img/topics/sorting-network-variation-words.png differ diff --git a/csunplugged/static/img/uc-computer-science-education-logo.png b/csunplugged/static/img/uc-computer-science-education-logo.png new file mode 100644 index 000000000..a5d49b0a7 Binary files /dev/null and b/csunplugged/static/img/uc-computer-science-education-logo.png differ diff --git a/csunplugged/static/js/bootstrap.min.js b/csunplugged/static/js/bootstrap.min.js new file mode 100644 index 000000000..d9c72dfc1 --- /dev/null +++ b/csunplugged/static/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v4.0.0-alpha.6 (https://getbootstrap.com) + * Copyright 2011-2017 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery. jQuery must be included before Bootstrap's JavaScript.");+function(t){var e=t.fn.jquery.split(" ")[0].split(".");if(e[0]<2&&e[1]<9||1==e[0]&&9==e[1]&&e[2]<1||e[0]>=4)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}(jQuery),+function(){function t(t,e){if(!t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!e||"object"!=typeof e&&"function"!=typeof e?t:e}function e(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function, not "+typeof e);t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),e&&(Object.setPrototypeOf?Object.setPrototypeOf(t,e):t.__proto__=e)}function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}var i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},o=function(){function t(t,e){for(var n=0;nthis._items.length-1||e<0)){if(this._isSliding)return void t(this._element).one(m.SLID,function(){return n.to(e)});if(i===e)return this.pause(),void this.cycle();var o=e>i?p.NEXT:p.PREVIOUS;this._slide(o,this._items[e])}},h.prototype.dispose=function(){t(this._element).off(l),t.removeData(this._element,a),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},h.prototype._getConfig=function(n){return n=t.extend({},_,n),r.typeCheckConfig(e,n,g),n},h.prototype._addEventListeners=function(){var e=this;this._config.keyboard&&t(this._element).on(m.KEYDOWN,function(t){return e._keydown(t)}),"hover"!==this._config.pause||"ontouchstart"in document.documentElement||t(this._element).on(m.MOUSEENTER,function(t){return e.pause(t)}).on(m.MOUSELEAVE,function(t){return e.cycle(t)})},h.prototype._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case d:t.preventDefault(),this.prev();break;case f:t.preventDefault(),this.next();break;default:return}},h.prototype._getItemIndex=function(e){return this._items=t.makeArray(t(e).parent().find(v.ITEM)),this._items.indexOf(e)},h.prototype._getItemByDirection=function(t,e){var n=t===p.NEXT,i=t===p.PREVIOUS,o=this._getItemIndex(e),r=this._items.length-1,s=i&&0===o||n&&o===r;if(s&&!this._config.wrap)return e;var a=t===p.PREVIOUS?-1:1,l=(o+a)%this._items.length;return l===-1?this._items[this._items.length-1]:this._items[l]},h.prototype._triggerSlideEvent=function(e,n){var i=t.Event(m.SLIDE,{relatedTarget:e,direction:n});return t(this._element).trigger(i),i},h.prototype._setActiveIndicatorElement=function(e){if(this._indicatorsElement){t(this._indicatorsElement).find(v.ACTIVE).removeClass(E.ACTIVE);var n=this._indicatorsElement.children[this._getItemIndex(e)];n&&t(n).addClass(E.ACTIVE)}},h.prototype._slide=function(e,n){var i=this,o=t(this._element).find(v.ACTIVE_ITEM)[0],s=n||o&&this._getItemByDirection(e,o),a=Boolean(this._interval),l=void 0,h=void 0,c=void 0;if(e===p.NEXT?(l=E.LEFT,h=E.NEXT,c=p.LEFT):(l=E.RIGHT,h=E.PREV,c=p.RIGHT),s&&t(s).hasClass(E.ACTIVE))return void(this._isSliding=!1);var d=this._triggerSlideEvent(s,c);if(!d.isDefaultPrevented()&&o&&s){this._isSliding=!0,a&&this.pause(),this._setActiveIndicatorElement(s);var f=t.Event(m.SLID,{relatedTarget:s,direction:c});r.supportsTransitionEnd()&&t(this._element).hasClass(E.SLIDE)?(t(s).addClass(h),r.reflow(s),t(o).addClass(l),t(s).addClass(l),t(o).one(r.TRANSITION_END,function(){t(s).removeClass(l+" "+h).addClass(E.ACTIVE),t(o).removeClass(E.ACTIVE+" "+h+" "+l),i._isSliding=!1,setTimeout(function(){return t(i._element).trigger(f)},0)}).emulateTransitionEnd(u)):(t(o).removeClass(E.ACTIVE),t(s).addClass(E.ACTIVE),this._isSliding=!1,t(this._element).trigger(f)),a&&this.cycle()}},h._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(a),o=t.extend({},_,t(this).data());"object"===("undefined"==typeof e?"undefined":i(e))&&t.extend(o,e);var r="string"==typeof e?e:o.slide;if(n||(n=new h(this,o),t(this).data(a,n)),"number"==typeof e)n.to(e);else if("string"==typeof r){if(void 0===n[r])throw new Error('No method named "'+r+'"');n[r]()}else o.interval&&(n.pause(),n.cycle())})},h._dataApiClickHandler=function(e){var n=r.getSelectorFromElement(this);if(n){var i=t(n)[0];if(i&&t(i).hasClass(E.CAROUSEL)){var o=t.extend({},t(i).data(),t(this).data()),s=this.getAttribute("data-slide-to");s&&(o.interval=!1),h._jQueryInterface.call(t(i),o),s&&t(i).data(a).to(s),e.preventDefault()}}},o(h,null,[{key:"VERSION",get:function(){return s}},{key:"Default",get:function(){return _}}]),h}();return t(document).on(m.CLICK_DATA_API,v.DATA_SLIDE,T._dataApiClickHandler),t(window).on(m.LOAD_DATA_API,function(){t(v.DATA_RIDE).each(function(){var e=t(this);T._jQueryInterface.call(e,e.data())})}),t.fn[e]=T._jQueryInterface,t.fn[e].Constructor=T,t.fn[e].noConflict=function(){return t.fn[e]=c,T._jQueryInterface},T}(jQuery),function(t){var e="collapse",s="4.0.0-alpha.6",a="bs.collapse",l="."+a,h=".data-api",c=t.fn[e],u=600,d={toggle:!0,parent:""},f={toggle:"boolean",parent:"string"},_={SHOW:"show"+l,SHOWN:"shown"+l,HIDE:"hide"+l,HIDDEN:"hidden"+l,CLICK_DATA_API:"click"+l+h},g={SHOW:"show",COLLAPSE:"collapse",COLLAPSING:"collapsing",COLLAPSED:"collapsed"},p={WIDTH:"width",HEIGHT:"height"},m={ACTIVES:".card > .show, .card > .collapsing",DATA_TOGGLE:'[data-toggle="collapse"]'},E=function(){function l(e,i){n(this,l),this._isTransitioning=!1,this._element=e,this._config=this._getConfig(i),this._triggerArray=t.makeArray(t('[data-toggle="collapse"][href="#'+e.id+'"],'+('[data-toggle="collapse"][data-target="#'+e.id+'"]'))),this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}return l.prototype.toggle=function(){t(this._element).hasClass(g.SHOW)?this.hide():this.show()},l.prototype.show=function(){var e=this;if(this._isTransitioning)throw new Error("Collapse is transitioning");if(!t(this._element).hasClass(g.SHOW)){var n=void 0,i=void 0;if(this._parent&&(n=t.makeArray(t(this._parent).find(m.ACTIVES)),n.length||(n=null)),!(n&&(i=t(n).data(a),i&&i._isTransitioning))){var o=t.Event(_.SHOW);if(t(this._element).trigger(o),!o.isDefaultPrevented()){n&&(l._jQueryInterface.call(t(n),"hide"),i||t(n).data(a,null));var s=this._getDimension();t(this._element).removeClass(g.COLLAPSE).addClass(g.COLLAPSING),this._element.style[s]=0,this._element.setAttribute("aria-expanded",!0),this._triggerArray.length&&t(this._triggerArray).removeClass(g.COLLAPSED).attr("aria-expanded",!0),this.setTransitioning(!0);var h=function(){t(e._element).removeClass(g.COLLAPSING).addClass(g.COLLAPSE).addClass(g.SHOW),e._element.style[s]="",e.setTransitioning(!1),t(e._element).trigger(_.SHOWN)};if(!r.supportsTransitionEnd())return void h();var c=s[0].toUpperCase()+s.slice(1),d="scroll"+c;t(this._element).one(r.TRANSITION_END,h).emulateTransitionEnd(u),this._element.style[s]=this._element[d]+"px"}}}},l.prototype.hide=function(){var e=this;if(this._isTransitioning)throw new Error("Collapse is transitioning");if(t(this._element).hasClass(g.SHOW)){var n=t.Event(_.HIDE);if(t(this._element).trigger(n),!n.isDefaultPrevented()){var i=this._getDimension(),o=i===p.WIDTH?"offsetWidth":"offsetHeight";this._element.style[i]=this._element[o]+"px",r.reflow(this._element),t(this._element).addClass(g.COLLAPSING).removeClass(g.COLLAPSE).removeClass(g.SHOW),this._element.setAttribute("aria-expanded",!1),this._triggerArray.length&&t(this._triggerArray).addClass(g.COLLAPSED).attr("aria-expanded",!1),this.setTransitioning(!0);var s=function(){e.setTransitioning(!1),t(e._element).removeClass(g.COLLAPSING).addClass(g.COLLAPSE).trigger(_.HIDDEN)};return this._element.style[i]="",r.supportsTransitionEnd()?void t(this._element).one(r.TRANSITION_END,s).emulateTransitionEnd(u):void s()}}},l.prototype.setTransitioning=function(t){this._isTransitioning=t},l.prototype.dispose=function(){t.removeData(this._element,a),this._config=null,this._parent=null,this._element=null,this._triggerArray=null,this._isTransitioning=null},l.prototype._getConfig=function(n){return n=t.extend({},d,n),n.toggle=Boolean(n.toggle),r.typeCheckConfig(e,n,f),n},l.prototype._getDimension=function(){var e=t(this._element).hasClass(p.WIDTH);return e?p.WIDTH:p.HEIGHT},l.prototype._getParent=function(){var e=this,n=t(this._config.parent)[0],i='[data-toggle="collapse"][data-parent="'+this._config.parent+'"]';return t(n).find(i).each(function(t,n){e._addAriaAndCollapsedClass(l._getTargetFromElement(n),[n])}),n},l.prototype._addAriaAndCollapsedClass=function(e,n){if(e){var i=t(e).hasClass(g.SHOW);e.setAttribute("aria-expanded",i),n.length&&t(n).toggleClass(g.COLLAPSED,!i).attr("aria-expanded",i)}},l._getTargetFromElement=function(e){var n=r.getSelectorFromElement(e);return n?t(n)[0]:null},l._jQueryInterface=function(e){return this.each(function(){var n=t(this),o=n.data(a),r=t.extend({},d,n.data(),"object"===("undefined"==typeof e?"undefined":i(e))&&e);if(!o&&r.toggle&&/show|hide/.test(e)&&(r.toggle=!1),o||(o=new l(this,r),n.data(a,o)),"string"==typeof e){if(void 0===o[e])throw new Error('No method named "'+e+'"');o[e]()}})},o(l,null,[{key:"VERSION",get:function(){return s}},{key:"Default",get:function(){return d}}]),l}();return t(document).on(_.CLICK_DATA_API,m.DATA_TOGGLE,function(e){e.preventDefault();var n=E._getTargetFromElement(this),i=t(n).data(a),o=i?"toggle":t(this).data();E._jQueryInterface.call(t(n),o)}),t.fn[e]=E._jQueryInterface,t.fn[e].Constructor=E,t.fn[e].noConflict=function(){return t.fn[e]=c,E._jQueryInterface},E}(jQuery),function(t){var e="dropdown",i="4.0.0-alpha.6",s="bs.dropdown",a="."+s,l=".data-api",h=t.fn[e],c=27,u=38,d=40,f=3,_={HIDE:"hide"+a,HIDDEN:"hidden"+a,SHOW:"show"+a,SHOWN:"shown"+a,CLICK:"click"+a,CLICK_DATA_API:"click"+a+l,FOCUSIN_DATA_API:"focusin"+a+l,KEYDOWN_DATA_API:"keydown"+a+l},g={BACKDROP:"dropdown-backdrop",DISABLED:"disabled",SHOW:"show"},p={BACKDROP:".dropdown-backdrop",DATA_TOGGLE:'[data-toggle="dropdown"]',FORM_CHILD:".dropdown form",ROLE_MENU:'[role="menu"]',ROLE_LISTBOX:'[role="listbox"]',NAVBAR_NAV:".navbar-nav",VISIBLE_ITEMS:'[role="menu"] li:not(.disabled) a, [role="listbox"] li:not(.disabled) a'},m=function(){function e(t){n(this,e),this._element=t,this._addEventListeners()}return e.prototype.toggle=function(){if(this.disabled||t(this).hasClass(g.DISABLED))return!1;var n=e._getParentFromElement(this),i=t(n).hasClass(g.SHOW);if(e._clearMenus(),i)return!1;if("ontouchstart"in document.documentElement&&!t(n).closest(p.NAVBAR_NAV).length){var o=document.createElement("div");o.className=g.BACKDROP,t(o).insertBefore(this),t(o).on("click",e._clearMenus)}var r={relatedTarget:this},s=t.Event(_.SHOW,r);return t(n).trigger(s),!s.isDefaultPrevented()&&(this.focus(),this.setAttribute("aria-expanded",!0),t(n).toggleClass(g.SHOW),t(n).trigger(t.Event(_.SHOWN,r)),!1)},e.prototype.dispose=function(){t.removeData(this._element,s),t(this._element).off(a),this._element=null},e.prototype._addEventListeners=function(){t(this._element).on(_.CLICK,this.toggle)},e._jQueryInterface=function(n){return this.each(function(){var i=t(this).data(s);if(i||(i=new e(this),t(this).data(s,i)),"string"==typeof n){if(void 0===i[n])throw new Error('No method named "'+n+'"');i[n].call(this)}})},e._clearMenus=function(n){if(!n||n.which!==f){var i=t(p.BACKDROP)[0];i&&i.parentNode.removeChild(i);for(var o=t.makeArray(t(p.DATA_TOGGLE)),r=0;r0&&a--,n.which===d&&adocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},h.prototype._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},h.prototype._checkScrollbar=function(){this._isBodyOverflowing=document.body.clientWidth=n){var i=this._targets[this._targets.length-1];return void(this._activeTarget!==i&&this._activate(i))}if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var o=this._offsets.length;o--;){var r=this._activeTarget!==this._targets[o]&&t>=this._offsets[o]&&(void 0===this._offsets[o+1]||t "+g.NAV_LINKS).addClass(_.ACTIVE),t(this._scrollElement).trigger(f.ACTIVATE,{relatedTarget:e})},h.prototype._clear=function(){t(this._selector).filter(g.ACTIVE).removeClass(_.ACTIVE)},h._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(a),o="object"===("undefined"==typeof e?"undefined":i(e))&&e; +if(n||(n=new h(this,o),t(this).data(a,n)),"string"==typeof e){if(void 0===n[e])throw new Error('No method named "'+e+'"');n[e]()}})},o(h,null,[{key:"VERSION",get:function(){return s}},{key:"Default",get:function(){return u}}]),h}();return t(window).on(f.LOAD_DATA_API,function(){for(var e=t.makeArray(t(g.DATA_SPY)),n=e.length;n--;){var i=t(e[n]);m._jQueryInterface.call(i,i.data())}}),t.fn[e]=m._jQueryInterface,t.fn[e].Constructor=m,t.fn[e].noConflict=function(){return t.fn[e]=c,m._jQueryInterface},m}(jQuery),function(t){var e="tab",i="4.0.0-alpha.6",s="bs.tab",a="."+s,l=".data-api",h=t.fn[e],c=150,u={HIDE:"hide"+a,HIDDEN:"hidden"+a,SHOW:"show"+a,SHOWN:"shown"+a,CLICK_DATA_API:"click"+a+l},d={DROPDOWN_MENU:"dropdown-menu",ACTIVE:"active",DISABLED:"disabled",FADE:"fade",SHOW:"show"},f={A:"a",LI:"li",DROPDOWN:".dropdown",LIST:"ul:not(.dropdown-menu), ol:not(.dropdown-menu), nav:not(.dropdown-menu)",FADE_CHILD:"> .nav-item .fade, > .fade",ACTIVE:".active",ACTIVE_CHILD:"> .nav-item > .active, > .active",DATA_TOGGLE:'[data-toggle="tab"], [data-toggle="pill"]',DROPDOWN_TOGGLE:".dropdown-toggle",DROPDOWN_ACTIVE_CHILD:"> .dropdown-menu .active"},_=function(){function e(t){n(this,e),this._element=t}return e.prototype.show=function(){var e=this;if(!(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&t(this._element).hasClass(d.ACTIVE)||t(this._element).hasClass(d.DISABLED))){var n=void 0,i=void 0,o=t(this._element).closest(f.LIST)[0],s=r.getSelectorFromElement(this._element);o&&(i=t.makeArray(t(o).find(f.ACTIVE)),i=i[i.length-1]);var a=t.Event(u.HIDE,{relatedTarget:this._element}),l=t.Event(u.SHOW,{relatedTarget:i});if(i&&t(i).trigger(a),t(this._element).trigger(l),!l.isDefaultPrevented()&&!a.isDefaultPrevented()){s&&(n=t(s)[0]),this._activate(this._element,o);var h=function(){var n=t.Event(u.HIDDEN,{relatedTarget:e._element}),o=t.Event(u.SHOWN,{relatedTarget:i});t(i).trigger(n),t(e._element).trigger(o)};n?this._activate(n,n.parentNode,h):h()}}},e.prototype.dispose=function(){t.removeClass(this._element,s),this._element=null},e.prototype._activate=function(e,n,i){var o=this,s=t(n).find(f.ACTIVE_CHILD)[0],a=i&&r.supportsTransitionEnd()&&(s&&t(s).hasClass(d.FADE)||Boolean(t(n).find(f.FADE_CHILD)[0])),l=function(){return o._transitionComplete(e,s,a,i)};s&&a?t(s).one(r.TRANSITION_END,l).emulateTransitionEnd(c):l(),s&&t(s).removeClass(d.SHOW)},e.prototype._transitionComplete=function(e,n,i,o){if(n){t(n).removeClass(d.ACTIVE);var s=t(n.parentNode).find(f.DROPDOWN_ACTIVE_CHILD)[0];s&&t(s).removeClass(d.ACTIVE),n.setAttribute("aria-expanded",!1)}if(t(e).addClass(d.ACTIVE),e.setAttribute("aria-expanded",!0),i?(r.reflow(e),t(e).addClass(d.SHOW)):t(e).removeClass(d.FADE),e.parentNode&&t(e.parentNode).hasClass(d.DROPDOWN_MENU)){var a=t(e).closest(f.DROPDOWN)[0];a&&t(a).find(f.DROPDOWN_TOGGLE).addClass(d.ACTIVE),e.setAttribute("aria-expanded",!0)}o&&o()},e._jQueryInterface=function(n){return this.each(function(){var i=t(this),o=i.data(s);if(o||(o=new e(this),i.data(s,o)),"string"==typeof n){if(void 0===o[n])throw new Error('No method named "'+n+'"');o[n]()}})},o(e,null,[{key:"VERSION",get:function(){return i}}]),e}();return t(document).on(u.CLICK_DATA_API,f.DATA_TOGGLE,function(e){e.preventDefault(),_._jQueryInterface.call(t(this),"show")}),t.fn[e]=_._jQueryInterface,t.fn[e].Constructor=_,t.fn[e].noConflict=function(){return t.fn[e]=h,_._jQueryInterface},_}(jQuery),function(t){if("undefined"==typeof Tether)throw new Error("Bootstrap tooltips require Tether (http://tether.io/)");var e="tooltip",s="4.0.0-alpha.6",a="bs.tooltip",l="."+a,h=t.fn[e],c=150,u="bs-tether",d={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:"0 0",constraints:[],container:!1},f={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"string",constraints:"array",container:"(string|element|boolean)"},_={TOP:"bottom center",RIGHT:"middle left",BOTTOM:"top center",LEFT:"middle right"},g={SHOW:"show",OUT:"out"},p={HIDE:"hide"+l,HIDDEN:"hidden"+l,SHOW:"show"+l,SHOWN:"shown"+l,INSERTED:"inserted"+l,CLICK:"click"+l,FOCUSIN:"focusin"+l,FOCUSOUT:"focusout"+l,MOUSEENTER:"mouseenter"+l,MOUSELEAVE:"mouseleave"+l},m={FADE:"fade",SHOW:"show"},E={TOOLTIP:".tooltip",TOOLTIP_INNER:".tooltip-inner"},v={element:!1,enabled:!1},T={HOVER:"hover",FOCUS:"focus",CLICK:"click",MANUAL:"manual"},I=function(){function h(t,e){n(this,h),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._isTransitioning=!1,this._tether=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}return h.prototype.enable=function(){this._isEnabled=!0},h.prototype.disable=function(){this._isEnabled=!1},h.prototype.toggleEnabled=function(){this._isEnabled=!this._isEnabled},h.prototype.toggle=function(e){if(e){var n=this.constructor.DATA_KEY,i=t(e.currentTarget).data(n);i||(i=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(n,i)),i._activeTrigger.click=!i._activeTrigger.click,i._isWithActiveTrigger()?i._enter(null,i):i._leave(null,i)}else{if(t(this.getTipElement()).hasClass(m.SHOW))return void this._leave(null,this);this._enter(null,this)}},h.prototype.dispose=function(){clearTimeout(this._timeout),this.cleanupTether(),t.removeData(this.element,this.constructor.DATA_KEY),t(this.element).off(this.constructor.EVENT_KEY),t(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&t(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._tether=null,this.element=null,this.config=null,this.tip=null},h.prototype.show=function(){var e=this;if("none"===t(this.element).css("display"))throw new Error("Please use show on visible elements");var n=t.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){if(this._isTransitioning)throw new Error("Tooltip is transitioning");t(this.element).trigger(n);var i=t.contains(this.element.ownerDocument.documentElement,this.element);if(n.isDefaultPrevented()||!i)return;var o=this.getTipElement(),s=r.getUID(this.constructor.NAME);o.setAttribute("id",s),this.element.setAttribute("aria-describedby",s),this.setContent(),this.config.animation&&t(o).addClass(m.FADE);var a="function"==typeof this.config.placement?this.config.placement.call(this,o,this.element):this.config.placement,l=this._getAttachment(a),c=this.config.container===!1?document.body:t(this.config.container);t(o).data(this.constructor.DATA_KEY,this).appendTo(c),t(this.element).trigger(this.constructor.Event.INSERTED),this._tether=new Tether({attachment:l,element:o,target:this.element,classes:v,classPrefix:u,offset:this.config.offset,constraints:this.config.constraints,addTargetClasses:!1}),r.reflow(o),this._tether.position(),t(o).addClass(m.SHOW);var d=function(){var n=e._hoverState;e._hoverState=null,e._isTransitioning=!1,t(e.element).trigger(e.constructor.Event.SHOWN),n===g.OUT&&e._leave(null,e)};if(r.supportsTransitionEnd()&&t(this.tip).hasClass(m.FADE))return this._isTransitioning=!0,void t(this.tip).one(r.TRANSITION_END,d).emulateTransitionEnd(h._TRANSITION_DURATION);d()}},h.prototype.hide=function(e){var n=this,i=this.getTipElement(),o=t.Event(this.constructor.Event.HIDE);if(this._isTransitioning)throw new Error("Tooltip is transitioning");var s=function(){n._hoverState!==g.SHOW&&i.parentNode&&i.parentNode.removeChild(i),n.element.removeAttribute("aria-describedby"),t(n.element).trigger(n.constructor.Event.HIDDEN),n._isTransitioning=!1,n.cleanupTether(),e&&e()};t(this.element).trigger(o),o.isDefaultPrevented()||(t(i).removeClass(m.SHOW),this._activeTrigger[T.CLICK]=!1,this._activeTrigger[T.FOCUS]=!1,this._activeTrigger[T.HOVER]=!1,r.supportsTransitionEnd()&&t(this.tip).hasClass(m.FADE)?(this._isTransitioning=!0,t(i).one(r.TRANSITION_END,s).emulateTransitionEnd(c)):s(),this._hoverState="")},h.prototype.isWithContent=function(){return Boolean(this.getTitle())},h.prototype.getTipElement=function(){return this.tip=this.tip||t(this.config.template)[0]},h.prototype.setContent=function(){var e=t(this.getTipElement());this.setElementContent(e.find(E.TOOLTIP_INNER),this.getTitle()),e.removeClass(m.FADE+" "+m.SHOW),this.cleanupTether()},h.prototype.setElementContent=function(e,n){var o=this.config.html;"object"===("undefined"==typeof n?"undefined":i(n))&&(n.nodeType||n.jquery)?o?t(n).parent().is(e)||e.empty().append(n):e.text(t(n).text()):e[o?"html":"text"](n)},h.prototype.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},h.prototype.cleanupTether=function(){this._tether&&this._tether.destroy()},h.prototype._getAttachment=function(t){return _[t.toUpperCase()]},h.prototype._setListeners=function(){var e=this,n=this.config.trigger.split(" ");n.forEach(function(n){if("click"===n)t(e.element).on(e.constructor.Event.CLICK,e.config.selector,function(t){return e.toggle(t)});else if(n!==T.MANUAL){var i=n===T.HOVER?e.constructor.Event.MOUSEENTER:e.constructor.Event.FOCUSIN,o=n===T.HOVER?e.constructor.Event.MOUSELEAVE:e.constructor.Event.FOCUSOUT;t(e.element).on(i,e.config.selector,function(t){return e._enter(t)}).on(o,e.config.selector,function(t){return e._leave(t)})}t(e.element).closest(".modal").on("hide.bs.modal",function(){return e.hide()})}),this.config.selector?this.config=t.extend({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},h.prototype._fixTitle=function(){var t=i(this.element.getAttribute("data-original-title"));(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},h.prototype._enter=function(e,n){var i=this.constructor.DATA_KEY;return n=n||t(e.currentTarget).data(i),n||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusin"===e.type?T.FOCUS:T.HOVER]=!0),t(n.getTipElement()).hasClass(m.SHOW)||n._hoverState===g.SHOW?void(n._hoverState=g.SHOW):(clearTimeout(n._timeout),n._hoverState=g.SHOW,n.config.delay&&n.config.delay.show?void(n._timeout=setTimeout(function(){n._hoverState===g.SHOW&&n.show()},n.config.delay.show)):void n.show())},h.prototype._leave=function(e,n){var i=this.constructor.DATA_KEY;if(n=n||t(e.currentTarget).data(i),n||(n=new this.constructor(e.currentTarget,this._getDelegateConfig()),t(e.currentTarget).data(i,n)),e&&(n._activeTrigger["focusout"===e.type?T.FOCUS:T.HOVER]=!1),!n._isWithActiveTrigger())return clearTimeout(n._timeout),n._hoverState=g.OUT,n.config.delay&&n.config.delay.hide?void(n._timeout=setTimeout(function(){n._hoverState===g.OUT&&n.hide()},n.config.delay.hide)):void n.hide()},h.prototype._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},h.prototype._getConfig=function(n){return n=t.extend({},this.constructor.Default,t(this.element).data(),n),n.delay&&"number"==typeof n.delay&&(n.delay={show:n.delay,hide:n.delay}),r.typeCheckConfig(e,n,this.constructor.DefaultType),n},h.prototype._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},h._jQueryInterface=function(e){return this.each(function(){var n=t(this).data(a),o="object"===("undefined"==typeof e?"undefined":i(e))&&e;if((n||!/dispose|hide/.test(e))&&(n||(n=new h(this,o),t(this).data(a,n)),"string"==typeof e)){if(void 0===n[e])throw new Error('No method named "'+e+'"');n[e]()}})},o(h,null,[{key:"VERSION",get:function(){return s}},{key:"Default",get:function(){return d}},{key:"NAME",get:function(){return e}},{key:"DATA_KEY",get:function(){return a}},{key:"Event",get:function(){return p}},{key:"EVENT_KEY",get:function(){return l}},{key:"DefaultType",get:function(){return f}}]),h}();return t.fn[e]=I._jQueryInterface,t.fn[e].Constructor=I,t.fn[e].noConflict=function(){return t.fn[e]=h,I._jQueryInterface},I}(jQuery));(function(r){var a="popover",l="4.0.0-alpha.6",h="bs.popover",c="."+h,u=r.fn[a],d=r.extend({},s.Default,{placement:"right",trigger:"click",content:"",template:''}),f=r.extend({},s.DefaultType,{content:"(string|element|function)"}),_={FADE:"fade",SHOW:"show"},g={TITLE:".popover-title",CONTENT:".popover-content"},p={HIDE:"hide"+c,HIDDEN:"hidden"+c,SHOW:"show"+c,SHOWN:"shown"+c,INSERTED:"inserted"+c,CLICK:"click"+c,FOCUSIN:"focusin"+c,FOCUSOUT:"focusout"+c,MOUSEENTER:"mouseenter"+c,MOUSELEAVE:"mouseleave"+c},m=function(s){function u(){return n(this,u),t(this,s.apply(this,arguments))}return e(u,s),u.prototype.isWithContent=function(){return this.getTitle()||this._getContent()},u.prototype.getTipElement=function(){return this.tip=this.tip||r(this.config.template)[0]},u.prototype.setContent=function(){var t=r(this.getTipElement());this.setElementContent(t.find(g.TITLE),this.getTitle()),this.setElementContent(t.find(g.CONTENT),this._getContent()),t.removeClass(_.FADE+" "+_.SHOW),this.cleanupTether()},u.prototype._getContent=function(){return this.element.getAttribute("data-content")||("function"==typeof this.config.content?this.config.content.call(this.element):this.config.content)},u._jQueryInterface=function(t){return this.each(function(){var e=r(this).data(h),n="object"===("undefined"==typeof t?"undefined":i(t))?t:null;if((e||!/destroy|hide/.test(t))&&(e||(e=new u(this,n),r(this).data(h,e)),"string"==typeof t)){if(void 0===e[t])throw new Error('No method named "'+t+'"');e[t]()}})},o(u,null,[{key:"VERSION",get:function(){return l}},{key:"Default",get:function(){return d}},{key:"NAME",get:function(){return a}},{key:"DATA_KEY",get:function(){return h}},{key:"Event",get:function(){return p}},{key:"EVENT_KEY",get:function(){return c}},{key:"DefaultType",get:function(){return f}}]),u}(s);return r.fn[a]=m._jQueryInterface,r.fn[a].Constructor=m,r.fn[a].noConflict=function(){return r.fn[a]=u,m._jQueryInterface},m})(jQuery)}(); \ No newline at end of file diff --git a/csunplugged/static/js/details-element-polyfill.js b/csunplugged/static/js/details-element-polyfill.js new file mode 100644 index 000000000..d7eb910a3 --- /dev/null +++ b/csunplugged/static/js/details-element-polyfill.js @@ -0,0 +1,5 @@ +/* +Details Element Polyfill 1.1.0 +Copyright © 2017 Javan Makhmali + */ +(function(){}).call(this),function(){var t,e,n,r,o,u,i,a,l,c;a={element:function(){var t,e,n,r,o;return e=document.createElement("details"),"open"in e?(e.innerHTML="ab",e.setAttribute("style","position: absolute; left: -9999px"),r=null!=(o=document.body)?o:document.documentElement,r.appendChild(e),t=e.offsetHeight,e.open=!0,n=e.offsetHeight,r.removeChild(e),t!==n):!1}(),toggleEvent:function(){var t;return t=document.createElement("details"),"ontoggle"in t}()},a.element&&a.toggleEvent||(o=function(){return document.head.insertAdjacentHTML("afterbegin",'')},r=function(){var t,e,n,r,o;return t=document.createElement("details").constructor.prototype,r=t.setAttribute,n=t.removeAttribute,o=null!=(e=Object.getOwnPropertyDescriptor(t,"open"))?e.set:void 0,Object.defineProperties(t,{open:{set:function(t){return"DETAILS"===this.tagName?(t?this.setAttribute("open",""):this.removeAttribute("open"),t):null!=o?o.call(this,t):void 0}},setAttribute:{value:function(t,e){return c(this,function(n){return function(){return r.call(n,t,e)}}(this))}},removeAttribute:{value:function(t){return c(this,function(e){return function(){return n.call(e,t)}}(this))}}})},u=function(){return n(function(t){return t.hasAttribute("open")?t.removeAttribute("open"):t.setAttribute("open","")})},i=function(){var t;return"undefined"!=typeof MutationObserver&&null!==MutationObserver?(t=new MutationObserver(function(t){var e,n,r,o,u,i;for(u=[],n=0,r=t.length;r>n;n++)o=t[n],i=o.target,e=o.attributeName,"DETAILS"===i.tagName&&"open"===e?u.push(l(i)):u.push(void 0);return u}),t.observe(document.documentElement,{attributes:!0,subtree:!0})):n(function(t){var e;return e=t.getAttribute("open"),setTimeout(function(){return t.getAttribute("open")!==e?l(t):void 0},1)})},t=function(t){return!(t.defaultPrevented||t.which>1||t.altKey||t.ctrlKey||t.metaKey||t.shiftKey||t.target.isContentEditable)},n=function(n){return addEventListener("click",function(r){var o,u;return t(r)&&(o=e(r.target,"SUMMARY"))&&"DETAILS"===(null!=(u=o.parentElement)?u.tagName:void 0)?n(o.parentElement):void 0},!1)},e=function(){return"function"==typeof Element.prototype.closest?function(t,e){return t.closest(e)}:function(t,e){for(;t;){if(t.tagName===e)return t;t=t.parentElement}}}(),l=function(t){var e;return e=document.createEvent("Events"),e.initEvent("toggle",!0,!1),t.dispatchEvent(e)},c=function(t,e){var n,r;return n=t.getAttribute("open"),r=e(),t.getAttribute("open")!==n&&l(t),r},a.element||(o(),r(),u()),a.element&&!a.toggleEvent&&i())}.call(this),function(){}.call(this); \ No newline at end of file diff --git a/csunplugged/static/js/jquery-3.1.1.min.js b/csunplugged/static/js/jquery-3.1.1.min.js new file mode 100644 index 000000000..4c5be4c0f --- /dev/null +++ b/csunplugged/static/js/jquery-3.1.1.min.js @@ -0,0 +1,4 @@ +/*! jQuery v3.1.1 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){"use strict";var c=[],d=a.document,e=Object.getPrototypeOf,f=c.slice,g=c.concat,h=c.push,i=c.indexOf,j={},k=j.toString,l=j.hasOwnProperty,m=l.toString,n=m.call(Object),o={};function p(a,b){b=b||d;var c=b.createElement("script");c.text=a,b.head.appendChild(c).parentNode.removeChild(c)}var q="3.1.1",r=function(a,b){return new r.fn.init(a,b)},s=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,t=/^-ms-/,u=/-([a-z])/g,v=function(a,b){return b.toUpperCase()};r.fn=r.prototype={jquery:q,constructor:r,length:0,toArray:function(){return f.call(this)},get:function(a){return null==a?f.call(this):a<0?this[a+this.length]:this[a]},pushStack:function(a){var b=r.merge(this.constructor(),a);return b.prevObject=this,b},each:function(a){return r.each(this,a)},map:function(a){return this.pushStack(r.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(f.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(a<0?b:0);return this.pushStack(c>=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):C.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/[^\x20\t\r\n\f]+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R), +a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,ka=/^$|\/(?:java|ecma)script/i,la={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};la.optgroup=la.option,la.tbody=la.tfoot=la.colgroup=la.caption=la.thead,la.th=la.td;function ma(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function na(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=ma(l.appendChild(f),"script"),j&&na(g),c){k=0;while(f=g[k++])ka.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var qa=d.documentElement,ra=/^key/,sa=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ta=/^([^.]*)(?:\.(.+)|)/;function ua(){return!0}function va(){return!1}function wa(){try{return d.activeElement}catch(a){}}function xa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)xa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=va;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(qa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i\x20\t\r\n\f]*)[^>]*)\/>/gi,za=/\s*$/g;function Da(a,b){return r.nodeName(a,"table")&&r.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function Ea(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Fa(a){var b=Ba.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ga(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&Aa.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ia(f,b,c,d)});if(m&&(e=pa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(ma(e,"script"),Ea),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=ma(h),f=ma(a),d=0,e=f.length;d0&&na(g,!i&&ma(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ja(this,a,!0)},remove:function(a){return Ja(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.appendChild(a)}})},prepend:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(ma(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!za.test(a)&&!la[(ja.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function Ya(a,b,c,d,e){return new Ya.prototype.init(a,b,c,d,e)}r.Tween=Ya,Ya.prototype={constructor:Ya,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=Ya.propHooks[this.prop];return a&&a.get?a.get(this):Ya.propHooks._default.get(this)},run:function(a){var b,c=Ya.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ya.propHooks._default.set(this),this}},Ya.prototype.init.prototype=Ya.prototype,Ya.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Ya.propHooks.scrollTop=Ya.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=Ya.prototype.init,r.fx.step={};var Za,$a,_a=/^(?:toggle|show|hide)$/,ab=/queueHooks$/;function bb(){$a&&(a.requestAnimationFrame(bb),r.fx.tick())}function cb(){return a.setTimeout(function(){Za=void 0}),Za=r.now()}function db(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ba[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function eb(a,b,c){for(var d,e=(hb.tweeners[b]||[]).concat(hb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?ib:void 0)), +void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&r.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),ib={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=jb[b]||r.find.attr;jb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=jb[g],jb[g]=e,e=null!=c(a,b,d)?g:null,jb[g]=f),e}});var kb=/^(?:input|select|textarea|button)$/i,lb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):kb.test(a.nodeName)||lb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function mb(a){var b=a.match(K)||[];return b.join(" ")}function nb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,nb(this)))});if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,nb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,nb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=nb(this),b&&V.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":V.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+mb(nb(c))+" ").indexOf(b)>-1)return!0;return!1}});var ob=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":r.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(ob,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:mb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,"events")||{})[b.type]&&V.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var tb=/\[\]$/,ub=/\r?\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)xb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ia.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,"\r\n")}}):{name:b.name,value:c.replace(ub,"\r\n")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\/\//,Fb={},Gb={},Hb="*/".concat("*"),Ib=d.createElement("a");Ib.href=qb.href;function Jb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:"GET",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Hb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+"").replace(Eb,qb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(K)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+"//"+Ib.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(yb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,"$1"),n=(sb.test(f)?"&":"?")+"_="+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Hb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&"withCredentials"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r(" + + + + + + {% block scripts %} + {% endblock scripts %} + + diff --git a/csunplugged/templates/columns.html b/csunplugged/templates/columns.html new file mode 100644 index 000000000..6d3adb6f7 --- /dev/null +++ b/csunplugged/templates/columns.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} + +{% block content_container %} +
+ {% block right_column_content %} + {% endblock right_column_content %} +
+
+ {% block left_column_content %} + {% endblock left_column_content %} +
+{% endblock content_container %} diff --git a/csunplugged/templates/dev/base.html b/csunplugged/templates/dev/base.html new file mode 100644 index 000000000..2c9d4a164 --- /dev/null +++ b/csunplugged/templates/dev/base.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block page_heading %} + +{% endblock page_heading %} diff --git a/csunplugged/templates/dev/index.html b/csunplugged/templates/dev/index.html new file mode 100644 index 000000000..21c3f22c0 --- /dev/null +++ b/csunplugged/templates/dev/index.html @@ -0,0 +1,102 @@ +{% extends "dev/base.html" %} + +{% load i18n %} +{% load static %} +{% load render_html_field %} + +{% block title %} + Dev Overview +{% endblock title %} + +{% block content %} +

Topics

+ {% if topics.all %} +
    + {% for topic in topics %} +
  • Topic: {{ topic }}
  • + +
      + {% if topic.units %} + {% for unit_plan in topic.units %} +
    • + Unit Plan: {{ unit_plan.name }} + {% if unit_plan.lessons %} + {% with grouped_lessons=unit_plan.grouped_lessons %} + {% include "topics/lessons-table.html" %} + {% endwith %} + {% endif %} +
    • + {% endfor %} + {% endif %} + + {% if topic.integrations.all %} +
    • Curriculum integrations: + {% with curriculum_integrations=topic.integrations.all %} + {% include "topics/curriculum-integrations-table.html" %} + {% endwith %} +
    • + {% endif %} + + {% if topic.programming_challenges %} +
    • Programming challenges: + {% with programming_challenges=topic.programming_challenges.all %} + {% include "topics/programming-challenges-table.html" %} + {% endwith %} +
    • + {% endif %} +
    + {% endfor %} +
+ {% endif %} + + {% if curriculum_areas %} +

Curriculum Areas

+
    + {% for area,children in curriculum_areas %} +
  • {% include "topics/curriculum-area-badge.html" %}
  • +
      + {% for area in children %} +
    • {% include "topics/curriculum-area-badge.html" %}
    • + {% endfor %} +
    + {% endfor %} +
+ {% endif %} + + {% if learning_outcomes.all %} +

Learning Outcomes

+
    + {% for learning_outcome in learning_outcomes.all %} +
  • + {{ learning_outcome.slug }} - {{ learning_outcome.text}}
    + {% for area in learning_outcome.curriculum_areas.all %} + {% include "topics/curriculum-area-badge.html" %} + {% endfor %} +
  • + {% endfor %} +
+ {% endif %} + +

Glossary Terms

+

+ View glossary +

+ + {% if programming_challenge_languages.all %} +

Programming Challenge Languages

+
    + {% for language in programming_challenge_languages.all %} +
  • {{ language }}
  • + {% endfor %} +
+ {% endif %} + + {% if programming_challenge_difficulties.all %} +

Programming Challenge Difficulties

+
    + {% for difficulty in programming_challenge_difficulties.all %} +
  • {{ difficulty.level }}: {{ difficulty.name }}
  • + {% endfor %} +
+ {% endif %} +{% endblock content %} diff --git a/csunplugged/templates/general/about.html b/csunplugged/templates/general/about.html new file mode 100644 index 000000000..1e615040c --- /dev/null +++ b/csunplugged/templates/general/about.html @@ -0,0 +1,46 @@ +{% extends "general/base.html" %} + +{% load i18n %} +{% load django_bootstrap_breadcrumbs %} + +{% block title %} + About +{% endblock title %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "About" "general:about" %} +{% endblock breadcrumbs %} + +{% block page_heading %} +

{% trans "About" %}

+{% endblock page_heading %} + +{% block left_column_content %} +

+ CS Unplugged is a collection of free learning activities that teach Computer Science through engaging games and puzzles that use cards, string, crayons and lots of running around. + We originally developed this so that young students could dive head-first into Computer Science, experiencing the kinds of questions and challenges that computer scientists experience, but without having to learn programming first. +

+ +

+ The collection was originally intended as a resource for outreach and extension, but with the adoption of computing and computational thinking into many classrooms around the world, it is now widely used for teaching. + The material has been used in many contexts outside the classroom as well, including science shows, talks for senior citizens, and special events. +

+ +

+ Thanks to generous sponsorships we have been able to create associated resources such as the videos, which are intended to help teachers see how the activities work (please don’t show them to your classes – let them experience the activities themselves!). + All of the activities that we provide are “open source” – they are released under a Creative Commons BY-NC-SA licence, so you can copy, share and modify the material. +

+ +

+ To view the team of contributors who work on this project, see our people page. +

+ +

+ For details on how to contact us, see our contact us page. +

+ +

+ For more information about the principles behind CS Unplugged, see our principles page. +

+{% endblock left_column_content %} diff --git a/csunplugged/templates/general/base.html b/csunplugged/templates/general/base.html new file mode 100644 index 000000000..961a19e8a --- /dev/null +++ b/csunplugged/templates/general/base.html @@ -0,0 +1,13 @@ +{% extends "columns.html" %} + +{% block right_column_content %} +

About pages

+ + +{% endblock right_column_content %} diff --git a/csunplugged/templates/general/computational-thinking.html b/csunplugged/templates/general/computational-thinking.html new file mode 100644 index 000000000..7cba1983f --- /dev/null +++ b/csunplugged/templates/general/computational-thinking.html @@ -0,0 +1,277 @@ +{% extends "general/base.html" %} + +{% load i18n %} +{% load static %} +{% load django_bootstrap_breadcrumbs %} + +{% block title %} + Computational Thinking and CS Unplugged +{% endblock title %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "Computational Thinking and CS Unplugged" "general:computational_thinking" %} +{% endblock breadcrumbs %} + +{% block page_heading %} +

{% trans "Computational Thinking and CS Unplugged" %}

+{% endblock page_heading %} + +{% block left_column_content %} +

What is Computational Thinking?

+ +

+ The world we live in has become a digital one, filled with technology and driven by Computer Science. + Software and technology have transformed every subject and job area, from science and medicine, to art history and psychology. + Digital technology is ubiquitous. + To be informed and empowered citizens, the next generation of students need to understand this digital world that they live in. +

+ +

+ This is why Computational Thinking has been called the ‘21st Century Skill Set’, and is important for everyone to learn. + It is critical to understanding how the digital world works, for harnessing the power of computers to solve tough problems, and making great things happen! + It also enables us to think critically about not just the benefits of certain technologies, but also the potential harm, ethical implications, or unintended consequences of these. +

+ +

But what exactly is Computational Thinking? Let’s have a look at a technical definition...

+ +
+

+ "Computational Thinking is the thought processes involved in formulating problems and their solutions so that the solutions are represented in a form that can be effectively carried out by an information-processing agent." +

+
+ Cuny, Snyder, Wing, 2010 +
+
+ +

+ Phew, it’s quite a mouthful isn’t it? + But, as we like to say at CS Unplugged, it’s just big words for simple ideas! + 'Information-processing agent' means anything that follows a set of instructions to complete a task (we call this 'computing'). + Most of the time this 'agent' means a computer or other type of digital device - but it could also be a human! + We’ll refer to it as a computer to make things a bit simpler. + To represent solutions in a way that a computer can carry them out, we have to represent them as a step by step process - an algorithm. + To create these algorithmic solutions we apply some special problem solving skills to. + These skill are what make up Computational Thinking! + And they are skills that are transferrable to any field. +

+ +

+ Computational Thinking could be described as 'thinking like a Computer Scientist', but it is now an important skill for everyone to learn, whether they want to be a Computer Scientist or not! + It’s interesting, and important, to note that Computational Thinking, and Computer Science, aren’t entirely about computers, they are more about people. + You might think that we write programs for computers, but really we write programs for people - to help them communicate, find information, and solve problems. +

+ +

+ For example, you might use an app on a smartphone to get directions to a friend's house; the app is an example of a computer program, and the smartphone is the "information processing agent" that runs the program for us. + Whoever designed the algorithm for working out the best route, and all the details like the interface and how to store the map, applied computational thinking to design the system. + But they didn't design it for the sake of the smartphone; they designed it to help the person using the smartphone. +

+ +

Computational Thinking in CS Unplugged

+ +

+ Throughout the lessons and units in CS Unplugged there are many links to Computational Thinking. + Teaching Computational Thinking through CS Unplugged activities teaches students how to: +

+ +
    +
  • describe a problem,
  • +
  • identify the important details needed to solve this problem,
  • +
  • break the problem down into small, logical steps,
  • +
  • use these steps to create a process (algorithm) that solves the problem,
  • +
  • and then evaluate this process.
  • +
+ +

These skills are transferable to any other curriculum area, but are particularly relevant to developing digital systems and solving problems using the capabilities of computers.

+ +

+ These Computational Thinking concepts are all connected to each other and support each other, but it's important to note that not all aspects of Computational Thinking will necessarily happen in every unit or lesson. + In each unit and lesson we've highlighted the important connections for you to observe your students in action. +

+ +

+ There are a number of definitions of Computational Thinking, but most have a set of 5 or 6 problem solving skills that Computational Thinking embodies. + For the Unplugged project we've identified the following six CT skills that are often mentioned in the literature; they are described below, and at the end of each Unplugged lesson we've identified ways that these skills appeared in the lesson, to help you see the CT connection with the lessons. +

+ +

Computational Thinking skills

+ +
+ + Algorithmic thinking + + +
+

+ Algorithms are at the heart of Computational Thinking and Computer Science, because in Computer Science the solutions to problems are not simply an answer (e.g. ‘42’, or a fact), they are algorithms. + An algorithm is a step-by-step process that solves a problem or completes a task. + If you follow the algorithm's steps correctly, you will arrive at a correct solution, even for different inputs. + For example, we can use an algorithm to find the shortest route between two locations on a map; the same algorithm can be used for any pair of starting and finishing points, so the solution depends on the input to the algorithm. + If we know the algorithm for solving a problem then we can solve that problem easily, whenever we want, without having to think! We can just follow the steps. + Computers can’t think for themselves, so they need to be given algorithms to do things. +

+ +

+ Algorithmic thinking is the process of creating algorithms. + When we create an algorithm to solve a problem, we call this an algorithmic solution. +

+ +

+ Computational algorithms (the kind that can run on digital devices) have relatively few ingredients because digital devices only have a few types of instruction that they can follow; the main things they can do are receive input, provide output, store values, follow instructions in a sequence, choose between options, and repeat instructions in a loop. + Despite how limited this range of instructions is, we've described everything that digital devices can compute, and this is why algorithms are described restricted to these elements. +

+
+
+ +
+ + Abstraction + + +
+

+ Abstraction is all about simplifying things to help us manage complexity. + It requires identifying what the most important aspects of a problem are and hiding the other specific details that we don’t need to focus on. + The important aspects can be used to create a model, or simplified representation, of the original thing we were dealing with. + We can then work with this model to solve the problem, rather than having to deal with all the nitty gritty details at once. + Computer Scientists often work with multiple levels of abstraction. +

+ +

+ We use abstraction often in our everyday lives, for example when we use maps. + Maps show us a simplified version of the world by leaving out unnecessary details, like where every individual tree in a park is, and only keeping the most relevant information the map reader will need, such as roads and street names. +

+ +

+ Digital devices use abstraction all the time; they try to hide as much unnecessary information from the user as possible. + For example, let's say you took a nice scenic photo on your last camping trip, and now you want to edit it on your laptop and adjust the colours in it. + Normally we could do this by opening a picture editing program, adjusting some colour sliders or maybe choosing a filter. + When you do this there are a lot of complicated operations happening which the computer is hiding from you. +

+ +
+ A picture with a magnifying glass showing the binary digits within the image. +
+ +

+ The picture you took is stored on the computer as a big list of pixels, which are each a different colour, and each colour is represented by a set of numbers, and each of these numbers are stored as binary digits! + That’s a lot of information. Imagine if when you adjusted the colours you had to go through and look at all the colour values of every pixel and change each and every one of those! + That’s what the computer is doing for you, but since you don’t need to know this to accomplish your goal the computer hides this information away. +

+
+
+ +
+ + Decomposition + + +
+

+ Decomposition is about breaking down problems into smaller, more manageable, parts, and then focusing on solving each of these smaller problems. + We can break a complex problem down until the smaller parts are so simple they become easy to solve. + The solutions to each of these smaller, and simpler, problems build up to a solution to the big problem we started with. + Decomposition helps make large problems much less intimidating! +

+ +

+ Decomposition is an important skill for creating algorithms and processes that can be implemented on a computing device, because computers need very specific instructions. + They need to be told each of the tiny steps they need to follow in order to do things. +

+ +

+ For example the overall task of making a cake can be decomposed into several smaller tasks, each of which can be performed easily. +

+ +

Make cake

+ +
    +
  1. Bake cake +
      +
    1. Put ingredients in bowl (butter, sugar, egg, flour)
    2. +
    3. Mix
    4. +
    5. Pour into tin
    6. +
    7. Put in oven for 30mins
    8. +
    9. Take out of tin
    10. +
    +
  2. +
  3. Make icing
  4. +
  5. Put on cake
  6. +
+
+
+ +
+ + Generalising and patterns + + +
+

+ Generalising is also referred to as 'pattern recognition and generalisation'. + Generalisation is taking a solution (or part of a solution) to a problem and generalising it so it can be applied to other similar problems and tasks. + Since solutions in Computer Science are algorithms, this means we take an algorithm and make it general enough that it can be used for a range of problems. + This process involves abstraction, because to make something more general we have to remove unnecessary details that are related to a specific problem or situation, but are not important to how the algorithm functions. +

+ +

+ Spotting patterns is an important part of this process, when we think about problems we might recognise similarities between them and that they can be solved in similar ways. + This is called pattern matching, and it’s something we do naturally all the time in our daily life. +

+ +

Generalised algorithms can be reused for a whole group of similar problems, which means we can come up with solutions quickly and effectively.

+
+
+ +
+ + Evaluation + + +
+

+ Evaluation is about identifying the possible solutions to a problem and judging which is the best to use, if they will work in some situations but not others, and how they can be improved. + When judging our solutions we need to think about a range of factors. + For example how much time it will take these processes (algorithms) to solve the problem and will it reliably solve the problem, or if there are certain situations where it will perform in a very different way. + Evaluation is something we do a lot in our everyday lives. +

+ +

+ There are different ways we can evaluate our algorithmic solutions. + We can test their speed by implementing them on a computer; or we can analyse them by counting or calculating how many steps they are likely to take. + We can test that they work correctly by giving our solution lots of different inputs, and checking it works as expected. + When we do this we need to think about the different inputs we test, because we don’t want to check every possible input (often there's an infinite number of different possible inputs!), but we still need to know if it will work for them. + Testing is something Computer Scientists and programmers do all the time. + But because we can't usually test every possible input, we also try to evaluate a system using logical reasoning. +

+
+
+ +
+ + Logic + + +
+

+ When trying to solve problems we need to think logically. + Logical reasoning is about trying to make sense of things by observing, collecting data, thinking about the facts you know, and then figuring things out based on what you already know. + It helps us use our existing knowledge to establish rules and check facts. +

+ +

+ For example, suppose you are writing software that works out the shortest route to a location from your house. + In the following map it's 2 minutes to the library if you head north from your house, but if you head south it's 3 minutes to the next intersection. + You might wonder if there's a better route to the library if you start by heading south, but logically there can't be because you'll already have walked for 3 minutes to get to the intersection. +

+ +

+ At a deeper level, computers are built entirely on logic. + They use 'True' and 'False' values, and use something called 'Boolean expressions', like “is age > 5”, to make decisions in computer programs. +

+ +

Tracking down a bug in a program also requires logical thinking, to work out where, and why, something in the program is going wrong.

+
+
+{% endblock left_column_content %} diff --git a/csunplugged/templates/general/contact.html b/csunplugged/templates/general/contact.html new file mode 100644 index 000000000..410dbfbb5 --- /dev/null +++ b/csunplugged/templates/general/contact.html @@ -0,0 +1,32 @@ +{% extends "general/base.html" %} + +{% load i18n %} +{% load django_bootstrap_breadcrumbs %} + +{% block title %} + Contact +{% endblock title %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "About" "general:about" %} + {% breadcrumb "Contact" "general:contact" %} +{% endblock breadcrumbs %} + +{% block page_heading %} +

{% trans "Contact Us" %}

+{% endblock page_heading %} + +{% block left_column_content %} +

+ Email: You can email the CS Unplugged team here. +

+ +

+ Twitter: @UCCSEd +

+ +

+ Community Google Group: We have a Google Group you can join and talk about CS Unplugged. +

+{% endblock left_column_content %} diff --git a/csunplugged/templates/general/index.html b/csunplugged/templates/general/index.html new file mode 100644 index 000000000..5abf3fe01 --- /dev/null +++ b/csunplugged/templates/general/index.html @@ -0,0 +1,62 @@ +{% extends "base.html" %} + +{% load i18n %} +{% load static %} + +{% block custom_title %} + CS Unplugged +{% endblock custom_title %} + +{% block body_container %} +
+
+
+

+ {% trans 'Computer Science' %}
+ {% trans 'without a computer' %} +

+
+
+ +
+
+ +
+
+

+ CS Unplugged is a collection of free teaching material that teaches Computer Science through engaging games and puzzles that use cards, string, crayons and lots of running around. +

+

+ The resources available to teachers include unit plans, lesson plans, teaching videos, curriculum integration activities, and programming exercises to plug in the Computer Science concepts they have just learnt unplugged. +

+
+
+ +
+
+ + + + + +
+{% endblock body_container %} diff --git a/csunplugged/templates/general/people.html b/csunplugged/templates/general/people.html new file mode 100644 index 000000000..45682c312 --- /dev/null +++ b/csunplugged/templates/general/people.html @@ -0,0 +1,135 @@ +{% extends "general/base.html" %} + +{% load i18n %} +{% load django_bootstrap_breadcrumbs %} + +{% block title %} + People +{% endblock title %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "About" "general:about" %} + {% breadcrumb "People" "general:people" %} +{% endblock breadcrumbs %} + +{% block page_heading %} +

{% trans "People" %}

+{% endblock page_heading %} + +{% block left_column_content %} +

Founders

+ +
    +
  • Tim Bell - University of Canterbury
  • +
  • Ian Witten - University of Waikato
  • +
  • Michael Fellows - Charles Darwin University
  • +
+ +

Advisers

+ +
    +
  • Craig Neville-Manning - Google
  • +
  • Peter J Denning - Computer Scientist
  • +
+ +

Current Team

+ +
    +
  • Tim Bell - Founder & Content Writer
  • +
  • Caitlin Duncan - Content Writer
  • +
  • Tracy Henderson - Content Writer
  • +
  • Behshid Ghorbani - Content Writer
  • +
  • Sarang Love Leehan - Creative Content Developer & Illustrator
  • +
  • Jack Morgan - Technical Team Lead
  • +
  • Hayley van Waas - Technical Team
  • +
  • Hayden Jackson - Technical Team
  • +
  • Jordan Griffiths - Technical Team
  • +
+ +

Classic CS Unplugged

+ +

Contributors

+ +
    +
  • Alfonso Rodriguez (USA)
  • +
  • Amitrajit Sarkar (New Zealand)
  • +
  • Anna Wingkvist (Sweden)
  • +
  • Anne Berry (France)
  • +
  • Benny Chor (Israel)
  • +
  • Bruce Webster (New Zealand)
  • +
  • Clara Eugenia
  • +
  • Constantine Mousafiris (Greece)
  • +
  • Faisal Hasan (Bangladesh)
  • +
  • Garza (Mexico)
  • +
  • Giovanni Michele Bianco (Italy)
  • +
  • Irina Derevianko (Russia)
  • +
  • Katharina Kranzdorf (Germany)
  • +
  • Long-Yuan Ya
  • +
  • Lorena Mendoza (United States)
  • +
  • Luciano Porto Barreto (Brazil)
  • +
  • Lukasz Nitschke (Poland)
  • +
  • Mohammed Obaid (New Zealand)
  • +
  • Mäxl Stege (Germany)
  • +
  • Paul Gibson (France)
  • +
  • Pawel Perekietka (Poland)
  • +
  • Professor Lee
  • +
  • Renzo Davoli Bologna (Italy)
  • +
  • Sertan Girgin (Turkey)
  • +
  • Shenglan Wang
  • +
  • Shimon Schocken (Israel)
  • +
  • Sook Kyoung Choi
  • +
  • SuYu (China)
  • +
  • Swami Manohar (India)
  • +
  • Theophanis Hatzigrivas (Greece)
  • +
  • Tom Verhoe
  • +
  • Yingchun Han (China)
  • +
+ +

Advisers

+ +
    +
  • Alfred Thompson
  • +
  • Allen Tucker
  • +
  • Andy Seymour (USA)
  • +
  • Anna Charny (US)
  • +
  • Bengt Aspvall (Sweden)
  • +
  • David Vogt
  • +
  • Den Tsutom Wada (Japan)
  • +
  • Geri Lorway (Canada)
  • +
  • Harold Thimbleby (UK)
  • +
  • Lee Won Gyu
  • +
  • Len Wagner (USA)
  • +
  • Lenore Blum (USA)
  • +
  • Lynn Lambert (USA)
  • +
  • Mindy Hard (USA)
  • +
  • Pam Hagen (Canada)
  • +
  • Paul Gibson (France)
  • +
  • Peter Henderson
  • +
  • Rachel Kestenbaum (USA)
  • +
  • Rick Dipaolo (USA)
  • +
  • Robb Cutler (USA)
  • +
  • Sam Chung (USA)
  • +
  • Susumu Kanemune (Japan)
  • +
  • Todd Seymour (USA)
  • +
  • Tom Cortina (USA)
  • +
  • Xie Xie (China)
  • +
+ +

Website

+ +
    +
  • Sam Jarman (New Zealand)
  • +
  • Tim Bell (New Zealand)
  • +
  • Isaac Freeman (New Zealand)
  • +
  • Renzo Davoli (Italy)
  • +
  • Su Yu (China)
  • +
  • Murugesh and Bruce Webster (New Zealand)
  • +
+ +

Parrots

+ +
    +
  • Arnold - Would like a cracker
  • +
+{% endblock left_column_content %} diff --git a/csunplugged/templates/general/principles.html b/csunplugged/templates/general/principles.html new file mode 100644 index 000000000..bbe8e0c1f --- /dev/null +++ b/csunplugged/templates/general/principles.html @@ -0,0 +1,131 @@ +{% extends "general/about.html" %} + +{% load i18n %} +{% load django_bootstrap_breadcrumbs %} + +{% block title %} + Principles +{% endblock title %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "About" "general:about" %} + {% breadcrumb "Principles" "general:principles" %} +{% endblock breadcrumbs %} + + +{% block page_heading %} +

{% trans "Principles" %}

+{% endblock page_heading %} + +{% block left_column_content %} +

+ The primary goal of the Unplugged project is to promote Computer Science (and computing in general) to young people as an interesting, engaging, and intellectually stimulating discipline. + We want to capture people’s imagination and address common misconceptions about what it means to be a computer scientist. + We want to convey fundamentals that do not depend on particular software or systems, ideas that will still be fresh in 10 years. + We want to reach kids in elementary schools and provide supplementary material for university courses. + We want to tread where high-tech educational solutions are infeasible; to cross the divide between the information-rich and information-poor, between industrialized countries and the developing world. +

+ +

+ There are many worthy projects for promoting Computer Science. + The main principles that distinguish the Unplugged activities are: +

+ +

No Computers Required

+ +

+ The activities do not depend on computers. + This avoids confusing Computer Science with programming or learning application software, makes the activities available to those who aren’t able to or don’t want to work with computers, and skips the barrier of learning to program before being able to explore ideas. + It also provides physical, kinaesthetic experiences as part of learning computing, which can be a welcome break from sitting in front of a screen. + For example, the parity magic trick is a card game that happens to use the same principle as error correction in computer memory. + Unplugged isn’t a completely Luddite approach – we do exploit the internet and other computing facilities to share and develop the activities, and we hope that students will have to opportunity to learn to program so that they can put wheels on the ideas they have been exploring. +

+ +

Real Computer Science

+ +

+ Unplugged presents fundamental concepts in Computer Science such as algorithms, artificial intelligence, graphics, information theory, human computer interfaces, programming languages, and so on. + We want to emphasize that programming is a means, not an end. + Wikipedia provides a definition of Computer Science, and Peter Denning’s Great Principles project provides a more detailed analysis of the topics it covers. +

+ +

Learning by doing

+ +

+ The activities tend to be kinaesthetic, often on a large scale and involving team work. + For example, the Sorting Network activity has teams of six running through a network drawn on the ground. + The activities tend to allow students to discover answers for themselves, rather than just being given solutions or algorithms to follow; that is, a constructivist approach is encouraged (where the teacher uses the scaffolding provided by Unplugged to ask questions that lead them to discover the knowledge themselves), as we want students to realize that they are capable of finding solutions to problems on their own, rather than being given a solution to apply to the problem. + For example, students don’t really need to be able to convert numbers to binary, but it is valuable for them to discover the patterns such as the doubling value of bits, patterns when you count in binary, and how the range increases exponentially as you add bits. +

+ +

Fun

+ +

+ The activities are fun and engaging, not just busy-work for the sake of it. + Usually the explanations are quite brief – the teacher lays out the materials and a few rules, and the students follow the challenge from there. + There are puzzles, challenges, competitions, problem solving and humour. + Unplugged activities should leave students with a sense of genuine achievement. + There is often a strong sense of story in the activities; problems are presented as part of a story rather than as an abstract mathematical challenge. + Children are more interested in pirates than privacy, and absurd fictitious stories can be more memorable than compelling business applications. +

+ +

No specialised equipment

+ +

+ The activities are low cost, using equipment commonly found in classrooms or stationery stores. + Most require only paper and pencil, and perhaps cards, string, chalk, whiteboard markers, balls or similar items. +

+ +

Variations encouraged

+ +

+ Unplugged is published under a Creative Commons licence, which permits free sharing (with acknowledgement). + Variations, adaptations and extensions are encouraged. + This also allows local publishing arrangements to take account of the kind of packaging that would make the material more accessible to local educational practitioners. +

+ +

+ Two specific situations that we get asked about are: +

+ +
    +
  • + For-profit classes (e.g. clubs with a subscription fee): It is fine to charge a fee for people to attend classes that you're running that uses this material. +
  • +
  • + Selling packs of support material (such as pre-printed cards): It is fine to do this. + We hope that you'll do it for a reasonable price, and it's even better if you can get someone to sponsor you so that you can give it away to educators, but the license allows you to set your own price. +
  • +
+ +

For everyone

+ +

+ The programme is strongly international – we encourage variations that are relevant to local cultures (for example, some activities that require a large playground can be changed to a board game for schools that have very little open space; others use contexts that might not be familiar to students in a different culture). + Translators should try the activities locally and involve teachers. + It is better to adapt activities rather than translate them faithfully to something that would be less meaningful in the local culture. + The activities are intended to be inclusive. +

+ +

Co-operative

+ +

+ We encourage co-operation, communication and problem solving. + Competition can also be effective if it is used appropriately, especially between teams rather than individuals, but having students working cooperatively is a great way to learn about problem solving. +

+ +

Stand-alone Activities

+ +

+ As much as possible, the activities are stand-alone modules that can be used independently of each other, so that they can be used for enrichment in curricula or for outreach on their own rather than having to be used as a series. + The ones that have been presented as lesson plans will sometimes require a series of lessons to be followed, but we will indicate if particular preparation is required. +

+ +

Resilient

+ +

+ The activities are resilient to errors made by students; they should not depend on getting many difficult steps exactly right, and minor mistakes should not prevent participants from understanding the principles. + The instructions are usually just one or two rules and a goal that can be expressed in a single sentence (e.g. “Each card is either fully visible or not; how can you display exactly 11 dots?”, or “We need to get from any house to any other house; what is the smallest number of paving stones that make this possible”). +

+{% endblock left_column_content %} diff --git a/csunplugged/templates/generic/import-stick-state-js.html b/csunplugged/templates/generic/import-stick-state-js.html new file mode 100644 index 000000000..8594ba22a --- /dev/null +++ b/csunplugged/templates/generic/import-stick-state-js.html @@ -0,0 +1,8 @@ +{% load static %} + + + diff --git a/csunplugged/templates/resources/base-resource-pdf.html b/csunplugged/templates/resources/base-resource-pdf.html new file mode 100644 index 000000000..7268affbd --- /dev/null +++ b/csunplugged/templates/resources/base-resource-pdf.html @@ -0,0 +1,39 @@ +{% load static %} + + + + {{ filename }} + + + +
+ {{ header_text }} +
+ {% for resource_image_data in resource_images %} + {% for resource_image in resource_image_data %} +
+ +
+ {% endfor %} + {% endfor %} +
+ - {{ resource.name }} - csunplugged.org +
+ + diff --git a/csunplugged/templates/resources/binary-cards-small.html b/csunplugged/templates/resources/binary-cards-small.html new file mode 100644 index 000000000..1e3bffba4 --- /dev/null +++ b/csunplugged/templates/resources/binary-cards-small.html @@ -0,0 +1,40 @@ +{% extends "resources/resource.html" %} + + +{% block description %} +

+ This resource contains printouts for binary numbers activities. +

+{% endblock description %} + +{% block generation_form %} +
+ Number of Bits + + +
+ + +
+ + +
+ +
+ Display Dot Counts + + +
+ + +
+ +
+ Black on Card Back + + +
+ + +
+{% endblock generation_form %} diff --git a/csunplugged/templates/resources/binary-cards.html b/csunplugged/templates/resources/binary-cards.html new file mode 100644 index 000000000..65408dff7 --- /dev/null +++ b/csunplugged/templates/resources/binary-cards.html @@ -0,0 +1,28 @@ +{% extends "resources/resource.html" %} + + +{% block description %} +

+ This resource contains printouts for binary numbers activities. +

+{% endblock description %} + +{% block generation_form %} +
+ Display Numbers + + +
+ + +
+ +
+ Black on Card Back + + +
+ + +
+{% endblock generation_form %} diff --git a/csunplugged/templates/resources/binary-to-alphabet.html b/csunplugged/templates/resources/binary-to-alphabet.html new file mode 100644 index 000000000..1188927ec --- /dev/null +++ b/csunplugged/templates/resources/binary-to-alphabet.html @@ -0,0 +1,19 @@ +{% extends "resources/resource.html" %} + + +{% block description %} +

+ This resource will show you how to use binary numbers to represent letters. +

+{% endblock description %} + +{% block generation_form %} +
+ Worksheet Version + + +
+ + +
+{% endblock generation_form %} diff --git a/csunplugged/templates/resources/binary-windows.html b/csunplugged/templates/resources/binary-windows.html new file mode 100644 index 000000000..0907b02e4 --- /dev/null +++ b/csunplugged/templates/resources/binary-windows.html @@ -0,0 +1,43 @@ +{% extends "resources/resource.html" %} + + +{% block description %} +

+ This resource contains printouts for binary numbers activities. +

+ + +{% endblock description %} + +{% block generation_form %} +
+ Number of Bits + + +
+ + +
+ +
+ Display Dot Counts + + +
+ + +
+ +
+ Value Representation + + +
+ + +
+{% endblock generation_form %} diff --git a/csunplugged/templates/resources/index.html b/csunplugged/templates/resources/index.html new file mode 100644 index 000000000..da2bdf695 --- /dev/null +++ b/csunplugged/templates/resources/index.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} + +{% load i18n %} +{% load static %} +{% load django_bootstrap_breadcrumbs %} + +{% block title %} + {% trans "Resources" %} +{% endblock title %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "Resources" "resources:index" %} +{% endblock breadcrumbs %} + +{% block page_heading %} +

{% trans "Resources" %}

+ +

+ This page diplays a complete list of all available resources. + If a lesson uses a resource, the lesson will contain a direct link to the + resource with a description on how to use it. +

+{% endblock page_heading %} + +{% block content %} + {% if all_resources %} + + {% else %} +

No resources are available.

+ {% endif %} +{% endblock content %} diff --git a/csunplugged/templates/resources/modulo-clock.html b/csunplugged/templates/resources/modulo-clock.html new file mode 100644 index 000000000..4ecef5416 --- /dev/null +++ b/csunplugged/templates/resources/modulo-clock.html @@ -0,0 +1,19 @@ +{% extends "resources/resource.html" %} + + +{% block description %} +

+ This resource contains printouts for modulo activities. +

+{% endblock description %} + +{% block generation_form %} +
+ Modulo + + +
+ + +
+{% endblock generation_form %} diff --git a/csunplugged/templates/resources/parity-cards.html b/csunplugged/templates/resources/parity-cards.html new file mode 100644 index 000000000..3314ecb86 --- /dev/null +++ b/csunplugged/templates/resources/parity-cards.html @@ -0,0 +1,54 @@ +{% extends "resources/resource.html" %} + + +{% block description %} +

+ This resource contains printouts for parity card activities. + To use this resource: +

+ +
    +
  • + Printed the PDF double sided. +
  • +
  • + Printing on 120 gsm card also can be useful. +
  • +
+ +

+ Each page contains 20 parity cards, so we recommend printing at least 2 + pages of cards as 40 cards is a useful number for most activities. +

+{% endblock description %} + +{% block generation_form %} +
+ Card back colour + +
+ +
+ +
+ +
+ +
+ +{% endblock generation_form %} diff --git a/csunplugged/templates/resources/resource.html b/csunplugged/templates/resources/resource.html new file mode 100644 index 000000000..0a120d6fb --- /dev/null +++ b/csunplugged/templates/resources/resource.html @@ -0,0 +1,115 @@ +{% extends "columns.html" %} + +{% load django_bootstrap_breadcrumbs %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "Resources" "resources:index" %} + {% breadcrumb resource.name "resources:resource" resource.slug %} +{% endblock breadcrumbs %} + +{% block title %} + {{ resource.name }} +{% endblock title %} + +{% block page_heading %} +

{{ resource.name }}

+ + {% block description %} + {% endblock description %} +{% endblock page_heading %} + +{% block left_column_content %} +

Create Resource

+ +
+ {% block generation_form %} + {% endblock generation_form %} + +
+ Paper Size + + +
+ + +
+ + {% if debug %} +
+

Local Generation Only

+ +
+ Header Text + +
+ + {% if resource.copies %} +
+ Number of Copies + +
+ {% endif %} + +
+ + {% else %} + {% if resource.copies %} + + {% endif %} + + + {% endif %} +
+{% endblock left_column_content %} + +{% block right_column_content %} + {% if thumbnail %} +

Example

+ {% load static %} + + {% endif %} +{% endblock right_column_content %} + +{% block end_content %} + {% if grouped_lessons %} +

Related Lessons

+ {% for age_group, lessons in grouped_lessons.items %} + + + + + + + + + + + {% for lesson in lessons %} + + + + + + + {% endfor %} + +
TopicAgesNumberLesson
+ + {{ lesson.topic.name }} + + + {{ age_group.ages.lower }} to {{ age_group.ages.upper }} + + {{ lesson.number }} + + + {{ lesson.name }} + +
+ {% endfor %} + + {% endif %} +{% endblock end_content %} diff --git a/csunplugged/templates/resources/sorting-network-cards.html b/csunplugged/templates/resources/sorting-network-cards.html new file mode 100644 index 000000000..45cf754c8 --- /dev/null +++ b/csunplugged/templates/resources/sorting-network-cards.html @@ -0,0 +1,57 @@ +{% extends "resources/resource.html" %} + + +{% block description %} +

+ These cards are to be used with the + Sorting Network resource. +

+{% endblock description %} + +{% block generation_form %} +
+ Card Type +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+{% endblock generation_form %} diff --git a/csunplugged/templates/resources/sorting-network.html b/csunplugged/templates/resources/sorting-network.html new file mode 100644 index 000000000..ff6c6cc6e --- /dev/null +++ b/csunplugged/templates/resources/sorting-network.html @@ -0,0 +1,30 @@ +{% extends "resources/resource.html" %} + + +{% block description %} +

+ This resource will show you how computers sort random numbers into + order using a thing called a sorting network. +

+

+ Can be used with the + Sorting Network cards resource. +

+{% endblock description %} + +{% block generation_form %} +
+ Prefill with Numbers + + +
+ + +
+ + +
+ + +
+{% endblock generation_form %} diff --git a/csunplugged/templates/resources/treasure-hunt.html b/csunplugged/templates/resources/treasure-hunt.html new file mode 100644 index 000000000..ce76f8919 --- /dev/null +++ b/csunplugged/templates/resources/treasure-hunt.html @@ -0,0 +1,31 @@ +{% extends "resources/resource.html" %} + + +{% block description %} +{% endblock description %} + +{% block generation_form %} +
+ Prefill with Numbers + + +
+ + +
+ + +
+ + +
+ +
+ Numbers Unsorted/Sorted + + +
+ + +
+{% endblock generation_form %} diff --git a/csunplugged/templates/topics/all-curriculum-integration-list.html b/csunplugged/templates/topics/all-curriculum-integration-list.html new file mode 100644 index 000000000..420a29bfd --- /dev/null +++ b/csunplugged/templates/topics/all-curriculum-integration-list.html @@ -0,0 +1,64 @@ +{% extends "base.html" %} + +{% load i18n %} +{% load static %} +{% load django_bootstrap_breadcrumbs %} + +{% block title %} + {% trans "Curriculum Integrations" %} +{% endblock title %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "Topics" "topics:index" %} + {% breadcrumb "Curriculum Integrations" "topics:all_curriculum_integrations" %} +{% endblock breadcrumbs %} + +{% block page_heading %} +

{% trans "Curriculum Integrations" %}

+

+ The following table lists curriculum integrations for all topics in the CS Unplugged content. +

+{% endblock page_heading %} + +{% block content %} + + + + + + + + + + + {% for curriculum_integration in curriculum_integrations %} + + + + + {% if curriculum_integration.has_prerequisite_lessons %} + + + {% endfor %} + +
TopicActivityCurriculum AreasPrerequisite Lessons?
+ {{ curriculum_integration.topic.name }} + + + + {{ curriculum_integration.name }} + + + + {% for area in curriculum_integration.curriculum_areas.all %} + {% include "topics/curriculum-area-badge.html" %} + {% endfor %} + + Yes + {% else %} + + No + {% endif %} +
+{% endblock %} diff --git a/csunplugged/templates/topics/computational-thinking-links.html b/csunplugged/templates/topics/computational-thinking-links.html new file mode 100644 index 000000000..571e52c7a --- /dev/null +++ b/csunplugged/templates/topics/computational-thinking-links.html @@ -0,0 +1,23 @@ +{% load render_html_field %} + +{% if computational_thinking_links %} + + +

+ Throughout the lessons there are links to computational thinking. + Below we've noted some general links that apply to this content. +

+ +

+ Teaching computational thinking through CSUnplugged activities supports students to learn how to describe a problem, identify what are the important details they need to solve this problem, and break it down into small, logical steps so that they can then create a process which solves the problem, and then evaluate this process. + These skills are transferable to any other curriculum area, but are particularly relevant to developing digital systems and solving problems using the capabilities of computers. +

+ +

+ These Computational Thinking concepts are all connected to each other and support each other, but it’s important to note that not all aspects of Computational Thinking happen in every unit or lesson. + We’ve highlighted the important connections for you to observe your students in action. + For more background information on what our definition of Computational Thinking see our notes about computational thinking. +

+ + {% render_html_field computational_thinking_links %} +{% endif %} diff --git a/csunplugged/templates/topics/curriculum-area-badge.html b/csunplugged/templates/topics/curriculum-area-badge.html new file mode 100644 index 000000000..e0a05d6d0 --- /dev/null +++ b/csunplugged/templates/topics/curriculum-area-badge.html @@ -0,0 +1,3 @@ + + {{ area }} + diff --git a/csunplugged/templates/topics/curriculum-integration.html b/csunplugged/templates/topics/curriculum-integration.html new file mode 100644 index 000000000..f559a0946 --- /dev/null +++ b/csunplugged/templates/topics/curriculum-integration.html @@ -0,0 +1,51 @@ +{% extends "columns.html" %} + +{% load static %} +{% load render_html_field %} +{% load django_bootstrap_breadcrumbs %} + +{% block title %} + {{ integration.name }} +{% endblock title %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "Topics" "topics:index" %} + {% breadcrumb topic.name "topics:topic" topic.slug %} + {% breadcrumb integration.name "topics:integration" topic.slug integration.slug %} +{% endblock breadcrumbs %} + +{% block page_heading %} +

{{ integration.name }}

+ + {% if integration_curriculum_areas %} +

+ {% for area in integration_curriculum_areas %} + {% include "topics/curriculum-area-badge.html" %} + {% endfor %} +

+ {% endif %} +{% endblock page_heading %} + +{% block left_column_content %} + {% render_html_field integration.content %} +{% endblock left_column_content %} + +{% block right_column_content %} + {% if prerequisite_lessons %} + + {% endif %} +{% endblock right_column_content %} diff --git a/csunplugged/templates/topics/curriculum-integrations-table.html b/csunplugged/templates/topics/curriculum-integrations-table.html new file mode 100644 index 000000000..701a12ec2 --- /dev/null +++ b/csunplugged/templates/topics/curriculum-integrations-table.html @@ -0,0 +1,35 @@ + + + + + + + + + + {% for curriculum_integration in curriculum_integrations %} + + + + {% if curriculum_integration.has_prerequisite_lessons %} + + + {% endfor %} + +
ActivityCurriculum AreasPrerequisite Lessons?
+ + + {{ curriculum_integration.name }} + + + + {% for area in curriculum_integration.curriculum_areas.all %} + {% include "topics/curriculum-area-badge.html" %} + {% endfor %} + + Yes + {% else %} + + No + {% endif %} +
diff --git a/csunplugged/templates/topics/glossary.html b/csunplugged/templates/topics/glossary.html new file mode 100644 index 000000000..7eafe3020 --- /dev/null +++ b/csunplugged/templates/topics/glossary.html @@ -0,0 +1,25 @@ +{% extends "columns.html" %} + +{% load render_html_field %} +{% load django_bootstrap_breadcrumbs %} + +{% block title %} + Glossary +{% endblock title %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "Topics" "topics:index" %} + {% breadcrumb "Glossary" "topics:glossary" %} +{% endblock breadcrumbs %} + +{% block page_heading %} +

Glossary

+{% endblock page_heading %} + +{% block left_column_content %} + {% for glossary_term in glossary_terms %} +

{{ glossary_term.term }}

+ {% render_html_field glossary_term.definition %} + {% endfor %} +{% endblock left_column_content %} diff --git a/csunplugged/templates/topics/index.html b/csunplugged/templates/topics/index.html new file mode 100644 index 000000000..9d795ff65 --- /dev/null +++ b/csunplugged/templates/topics/index.html @@ -0,0 +1,34 @@ +{% extends "base.html" %} + +{% load i18n %} +{% load static %} +{% load django_bootstrap_breadcrumbs %} + +{% block title %} + {% trans "Topics" %} +{% endblock title %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "Topics" "topics:index" %} +{% endblock breadcrumbs %} + +{% block page_heading %} +

{% trans "Topics" %}

+{% endblock page_heading %} + +{% block content %} + {% if all_topics %} +

Open a topic to see all related unit plans, lessons, curriculum integrations, and programming challenges.

+ + {% else %} +

No topics are available.

+ {% endif %} +{% endblock content %} diff --git a/csunplugged/templates/topics/lesson.html b/csunplugged/templates/topics/lesson.html new file mode 100644 index 000000000..2b4802454 --- /dev/null +++ b/csunplugged/templates/topics/lesson.html @@ -0,0 +1,107 @@ +{% extends "columns.html" %} + +{% load static %} +{% load render_html_field %} +{% load django_bootstrap_breadcrumbs %} + +{% block title %} + {{ lesson.name }} +{% endblock title %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "Topics" "topics:index" %} + {% breadcrumb topic.name "topics:topic" topic.slug %} + {% breadcrumb unit_plan.name "topics:unit_plan" topic.slug unit_plan.slug %} + {% breadcrumb lesson.name "topics:lesson" topic.slug unit_plan.slug lesson.slug %} +{% endblock breadcrumbs %} + +{% block page_heading %} +

{{ lesson.name }}

+
    +
  • Duration: {{ lesson.duration }} minutes
  • + {% for lesson_age in lesson_ages %} +
  • Ages {{ lesson_age.lower }} to {{ lesson_age.upper }}: Lesson {{ lesson_age.number }}
  • + {% endfor %} +
+{% endblock page_heading %} + +{% block left_column_content %} + + {% if learning_outcomes %} +

Learning outcomes

+

Students will be able to:

+
    + {% for learning_outcome in learning_outcomes %} +
  • + {{ learning_outcome.text }} + {% for area in learning_outcome.curriculum_areas.all %} + {% include "topics/curriculum-area-badge.html" %} + {% endfor %} +
  • + {% endfor %} +
+ {% endif %} + + {% render_html_field lesson.content %} + + {% with computational_thinking_links=lesson.computational_thinking_links %} + {% include "topics/computational-thinking-links.html" %} + {% endwith %} +{% endblock left_column_content %} + +{% block right_column_content %} + {% if generated_resources %} +

CS Unplugged resources

+ + {% endif %} + + {% if lesson.classroom_resources %} +

Classroom resources

+
    + {% for classroom_resource in lesson.classroom_resources %} +
  • {{ classroom_resource }}
  • + {% endfor %} +
+ {% endif %} + + {% if programming_challenges %} +

Programming challenges

+

+ + View related programming challenges + +

+ {% endif %} + + +{% endblock right_column_content %} + +{% block scripts %} + {% include "generic/import-stick-state-js.html" %} +{% endblock scripts %} diff --git a/csunplugged/templates/topics/lessons-table.html b/csunplugged/templates/topics/lessons-table.html new file mode 100644 index 000000000..c239ab5a9 --- /dev/null +++ b/csunplugged/templates/topics/lessons-table.html @@ -0,0 +1,43 @@ +{% for age_group, lessons in grouped_lessons.items %} + + + + + + + + + {% if age_group.description %} + + + + {% endif %} + {% for lesson in lessons %} + + + + {% if lesson.has_programming_challenges %} + + + {% endfor %} + +
+ Ages {{ age_group.ages.lower }} to {{ age_group.ages.upper }} + Programming challenges
+ {{ age_group.description }} +
+ {{ lesson.number }} + + + {{ lesson.name }} + + + + Yes + + {% else %} + + No + {% endif %} +
+{% endfor %} diff --git a/csunplugged/templates/topics/programming-challenge-language-solution.html b/csunplugged/templates/topics/programming-challenge-language-solution.html new file mode 100644 index 000000000..46cea6a59 --- /dev/null +++ b/csunplugged/templates/topics/programming-challenge-language-solution.html @@ -0,0 +1,63 @@ +{% extends "base.html" %} + +{% load static %} +{% load render_html_field %} +{% load django_bootstrap_breadcrumbs %} + +{% block title %} + {{ programming_challenge.challenge_set_number }}.{{ programming_challenge.challenge_number }} + {{ programming_challenge.name }} solution +{% endblock title %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "Topics" "topics:index" %} + {% breadcrumb topic.name "topics:topic" topic.slug %} + {% breadcrumb programming_challenge.name "topics:programming_challenge" topic.slug programming_challenge.slug %} + {% breadcrumb "Solution" "" %} +{% endblock breadcrumbs %} + +{% block page_heading %} +

+ {{ programming_challenge.challenge_set_number }}.{{ programming_challenge.challenge_number }} + {{ programming_challenge.name }} +

+

+ + {{ implementation.language.name }} solution +

+{% endblock page_heading %} + +{% block content %} + + +
+ + View solution + + +
+

This is just one of many possible solutions:

+ {% render_html_field implementation.solution %} +
+
+ +

+ + Back to programming challenge + +

+{% endblock content %} + +{% block end_content %} + {% if programming_challenge.extra_challenge %} +
+
+

Extra Challenge

+ {% render_html_field programming_challenge.extra_challenge %} +
+
+ {% endif %} +{% endblock end_content %} diff --git a/csunplugged/templates/topics/programming-challenge-lesson-list.html b/csunplugged/templates/topics/programming-challenge-lesson-list.html new file mode 100644 index 000000000..f3dfa5e24 --- /dev/null +++ b/csunplugged/templates/topics/programming-challenge-lesson-list.html @@ -0,0 +1,31 @@ +{% extends "base.html" %} + +{% load render_html_field %} +{% load django_bootstrap_breadcrumbs %} + +{% block title %} + {{ lesson.name }} programming challenges +{% endblock title %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "Topics" "topics:index" %} + {% breadcrumb topic.name "topics:topic" topic.slug %} + {% breadcrumb "Programming Challenges" "topics:programming_challenges_list" topic.slug unit_plan.slug lesson.slug %} +{% endblock breadcrumbs %} + +{% block page_heading %} +

{{ lesson.name }} programming challenges

+{% endblock page_heading %} + +{% block content %} + {% if lesson.programming_challenges_description %} + {% render_html_field lesson.programming_challenges_description %} + {% endif %} + + {% if programming_challenges %} + {% include "topics/programming-challenges-table.html" %} + {% else %} +

No programming challenges for {{ topic }}.

+ {% endif %} +{% endblock content %} diff --git a/csunplugged/templates/topics/programming-challenge.html b/csunplugged/templates/topics/programming-challenge.html new file mode 100644 index 000000000..c57141445 --- /dev/null +++ b/csunplugged/templates/topics/programming-challenge.html @@ -0,0 +1,111 @@ +{% extends "base.html" %} + +{% load static %} +{% load render_html_field %} +{% load django_bootstrap_breadcrumbs %} + +{% block title %} + {{ programming_challenge.name }} +{% endblock title %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "Topics" "topics:index" %} + {% breadcrumb topic.name "topics:topic" topic.slug %} + {% breadcrumb programming_challenge.name "topics:programming_challenge" topic.slug programming_challenge.slug %} +{% endblock breadcrumbs %} + +{% block page_heading %} +

+ {{ programming_challenge.name }} +

+ +

+ Challenge Level: {{ programming_challenge.difficulty.name }} +

+{% endblock page_heading %} + +{% block content_container %} +
+ {% if lessons %} + + {% endif %} + +

Learning outcomes

+ {% if learning_outcomes %} +
    + {% for learning_outcome in learning_outcomes %} +
  • + {{ learning_outcome.text }} + {% for area in learning_outcome.curriculum_areas.all %} + {% include "topics/curriculum-area-badge.html" %} + {% endfor %} +
  • + {% endfor %} +
+ {% else %} +

No learning outcomes listed for {{ programming_challenge.name }}.

+ {% endif %} + + {% render_html_field programming_challenge.content %} +
+
+

Languages

+ + {% for implementation in implementations %} +
+ + + + {{ implementation.language.name }} + + + +
+

What it should look like

+ {% render_html_field implementation.expected_result %} + + {% if implementation.hints %} +
+ + + Hints + + + +
+ {% render_html_field implementation.hints %} +
+
+ {% endif %} + +

+ + Show {{ implementation.language.name }} solution + +

+
+
+ {% endfor %} +
+{% endblock content_container %} + +{% block end_content %} + {% if programming_challenge.extra_challenge %} +

Extra Challenge

+ {% render_html_field programming_challenge.extra_challenge %} + {% endif %} +{% endblock end_content %} diff --git a/csunplugged/templates/topics/programming-challenges-table.html b/csunplugged/templates/topics/programming-challenges-table.html new file mode 100644 index 000000000..90cd97841 --- /dev/null +++ b/csunplugged/templates/topics/programming-challenges-table.html @@ -0,0 +1,36 @@ +{% load static %} + + + + + + + + + + + + {% for programming_challenge in programming_challenges %} + + + + + + + {% endfor %} + +
NumberNameChallenge LevelLanguages
+ {{ programming_challenge.challenge_set_number }}.{{ programming_challenge.challenge_number }} + + + + {{ programming_challenge.name }} + + + + {% include "topics/programming-difficulty-badge.html" %} + + {% for implementation in programming_challenge.ordered_implementations %} + + {% endfor %} +
diff --git a/csunplugged/templates/topics/programming-difficulty-badge.html b/csunplugged/templates/topics/programming-difficulty-badge.html new file mode 100644 index 000000000..cdfb4ccf2 --- /dev/null +++ b/csunplugged/templates/topics/programming-difficulty-badge.html @@ -0,0 +1,3 @@ + + {{ programming_challenge.difficulty.name }} + diff --git a/csunplugged/templates/topics/topic-other-resources.html b/csunplugged/templates/topics/topic-other-resources.html new file mode 100644 index 000000000..eacd5f77a --- /dev/null +++ b/csunplugged/templates/topics/topic-other-resources.html @@ -0,0 +1,25 @@ +{% extends "columns.html" %} + +{% load render_html_field %} +{% load django_bootstrap_breadcrumbs %} + +{% block title %} + {{ topic.name }} other resources +{% endblock title %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "Topics" "topics:index" %} + {% breadcrumb topic.name "topics:topic" topic.slug %} + {% breadcrumb "Other Resources" "topics:other_resources" topic.slug %} +{% endblock breadcrumbs %} + +{% block page_heading %} +

{{ topic.name }}

+{% endblock page_heading %} + +{% block left_column_content %} +

Other resources

+ + {% render_html_field topic.other_resources %} +{% endblock left_column_content %} diff --git a/csunplugged/templates/topics/topic.html b/csunplugged/templates/topics/topic.html new file mode 100644 index 000000000..3ca8456e4 --- /dev/null +++ b/csunplugged/templates/topics/topic.html @@ -0,0 +1,103 @@ +{% extends "columns.html" %} + +{% load static %} +{% load render_html_field %} +{% load django_bootstrap_breadcrumbs %} + +{% block title %} + {{ topic.name }} +{% endblock title %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "Topics" "topics:index" %} + {% breadcrumb topic.name "topics:topic" topic.slug %} +{% endblock breadcrumbs %} + +{% block page_heading %} +

{{ topic.name }}

+{% endblock page_heading %} + +{% block left_column_content %} + {% render_html_field topic.content %} + + {% for unit_plan in unit_plans %} + + + {% if unit_plan.grouped_lessons %} +

+ or jump straight to a lesson of the unit: +

+ {% endif %} + {% if unit_plan.grouped_lessons %} + {% with grouped_lessons=unit_plan.grouped_lessons %} + {% include "topics/lessons-table.html" %} + {% endwith %} + {% endif %} + {% endfor %} + + {% if curriculum_integrations %} +

Curriculum Integrations

+ {% include "topics/curriculum-integrations-table.html" %} + {% endif %} + + {% if resources %} +

Resources

+

+ The following resources are used in {{ topic.name }} lessons, and can + be accessed here (and also on each lesson page). +

+ + {% endif %} + + {% if topic.other_resources %} +

Looking for more?

+

+ + Click here for other resources + +

+ {% endif %} +{% endblock left_column_content %} + +{% block right_column_content %} + +{% endblock right_column_content %} + +{% block scripts %} + {% include "generic/import-stick-state-js.html" %} +{% endblock scripts %} diff --git a/csunplugged/templates/topics/unit-plan.html b/csunplugged/templates/topics/unit-plan.html new file mode 100644 index 000000000..5169a934c --- /dev/null +++ b/csunplugged/templates/topics/unit-plan.html @@ -0,0 +1,60 @@ +{% extends "columns.html" %} + +{% load render_html_field %} +{% load django_bootstrap_breadcrumbs %} + +{% block title %} + {{ unit_plan.name }} +{% endblock title %} + +{% block breadcrumbs %} + {% breadcrumb "Home" "/" %} + {% breadcrumb "Topics" "topics:index" %} + {% breadcrumb topic.name "topics:topic" topic.slug %} + {% breadcrumb unit_plan.name "topics:unit_plan" topic.slug unit_plan.slug %} +{% endblock breadcrumbs %} + +{% block page_heading %} +

{{ unit_plan.name }}

+{% endblock page_heading %} + +{% block left_column_content %} + {% render_html_field unit_plan.content %} + + {% with computational_thinking_links=unit_plan.computational_thinking_links %} + {% include "topics/computational-thinking-links.html" %} + {% endwith %} + + {% if grouped_lessons %} +

Lessons

+ {% include "topics/lessons-table.html" %} + {% endif %} +{% endblock left_column_content %} + +{% block right_column_content %} + +{% endblock right_column_content %} + +{% block scripts %} + {% include "generic/import-stick-state-js.html" %} +{% endblock scripts %} diff --git a/csunplugged/tests/BaseTest.py b/csunplugged/tests/BaseTest.py new file mode 100644 index 000000000..360339fbf --- /dev/null +++ b/csunplugged/tests/BaseTest.py @@ -0,0 +1,40 @@ +"""Base test class with methods implemented for Django testing.""" + +from django.test import SimpleTestCase +from django.test.client import Client +from django.utils.translation import activate + + +class BaseTest(SimpleTestCase): + """Base test class with methods implemented for Django testing.""" + + def __init__(self, *args, **kwargs): + """Create the BaseTest object by calling the parent's constructor.""" + super().__init__(*args, **kwargs) + self.language = None + + @classmethod + def setUpClass(cls): + """Automatically called before tests in class.""" + super(BaseTest, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + """Automatically called after each test.""" + super(BaseTest, cls).tearDownClass() + + def setUp(self): + """Automatically called before each test. + + Sets the language if specified and creates a new client. + """ + if self.language is not None: + activate(self.language) + self.client = Client() + + def tearDown(self): + """Automatically called after each test. + + Deletes test user. + """ + pass diff --git a/csunplugged/tests/BaseTestWithDB.py b/csunplugged/tests/BaseTestWithDB.py new file mode 100644 index 000000000..282ee55f5 --- /dev/null +++ b/csunplugged/tests/BaseTestWithDB.py @@ -0,0 +1,52 @@ +"""Base test class with methods implemented for Django testing.""" + +from django.test import TestCase +from django.contrib.auth.models import User +from django.test.client import Client +from django.utils.translation import activate + + +class BaseTestWithDB(TestCase): + """Base test class with methods implemented for Django testing.""" + + def __init__(self, *args, **kwargs): + """Create a BaseTestWithDB object.""" + super().__init__(*args, **kwargs) + self.language = None + + @classmethod + def setUpTestData(cls): + """Create data for the whole class. + + Creates a new user. + """ + super(BaseTestWithDB, cls).setUpTestData() + cls.username = "test" + cls.email = "test@test.com" + cls.password = "test" + cls.test_user = User.objects.create_user(cls.username, cls.email, cls.password) + + @classmethod + def setUpClass(cls): + """Automatically called before tests in class.""" + super(BaseTestWithDB, cls).setUpClass() + cls.client = Client() + + @classmethod + def tearDownClass(cls): + """Automatically called after tests in class.""" + super(BaseTestWithDB, cls).tearDownClass() + + def setUp(self): + """Automatically called before each test. + + Sets the language if specified, logs into the database + """ + if self.language is not None: + activate(self.language) + login = self.client.login(username=self.username, password=self.password) + self.assertEqual(login, True) + + def tearDown(self): + """Automatically called after each test. Deletes the user.""" + pass diff --git a/csunplugged/tests/__init__.py b/csunplugged/tests/__init__.py new file mode 100644 index 000000000..63495f9fd --- /dev/null +++ b/csunplugged/tests/__init__.py @@ -0,0 +1 @@ +"""Module for test cases for Django system.""" diff --git a/csunplugged/tests/general/__init__.py b/csunplugged/tests/general/__init__.py new file mode 100644 index 000000000..f0a4ac5ab --- /dev/null +++ b/csunplugged/tests/general/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the general application.""" diff --git a/csunplugged/tests/general/management/__init__.py b/csunplugged/tests/general/management/__init__.py new file mode 100644 index 000000000..635d68b1e --- /dev/null +++ b/csunplugged/tests/general/management/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the template tags in the general application.""" diff --git a/csunplugged/tests/general/management/test_commands.py b/csunplugged/tests/general/management/test_commands.py new file mode 100644 index 000000000..0fbddfdf8 --- /dev/null +++ b/csunplugged/tests/general/management/test_commands.py @@ -0,0 +1,17 @@ +"""Module for the testing custom Django commands.""" + +from tests.BaseTestWithDB import BaseTestWithDB +from django.core import management + + +class ManagementCommandTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_updatedata_command(self): + management.call_command("updatedata") + + def test_makeresources_command(self): + management.call_command("makeresources") diff --git a/csunplugged/tests/general/templatetags/__init__.py b/csunplugged/tests/general/templatetags/__init__.py new file mode 100644 index 000000000..635d68b1e --- /dev/null +++ b/csunplugged/tests/general/templatetags/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the template tags in the general application.""" diff --git a/csunplugged/tests/general/templatetags/test_render_html_field.py b/csunplugged/tests/general/templatetags/test_render_html_field.py new file mode 100644 index 000000000..cfdb03aa4 --- /dev/null +++ b/csunplugged/tests/general/templatetags/test_render_html_field.py @@ -0,0 +1,111 @@ +from django import template + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + +from topics.models import CurriculumIntegration + + +class RenderHTMLFieldTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = TopicsTestDataGenerator() + + def render_template(self, string, context=None): + context = context or {} + context = template.Context(context) + return template.Template(string).render(context) + + def test_render_html_field_without_static(self): + topic = self.test_data.create_topic(1) + curriculum_integration_without_static = self.test_data.create_integration( + topic=topic, + number=1, + ) + context = {"integration": curriculum_integration_without_static} + rendered = self.render_template( + "{% load render_html_field %}\n{% render_html_field integration.content %}", + context + ) + self.assertHTMLEqual(rendered, "

Content for integration 1.

") + + def test_render_html_field_with_static(self): + topic = self.test_data.create_topic(1) + curriculum_integration_with_static = CurriculumIntegration.objects.create( + topic=topic, + slug="slug-2", + number=2, + name="2", + content="" + ) + context = {"integration": curriculum_integration_with_static} + rendered = self.render_template( + "{% load render_html_field %}\n{% render_html_field integration.content %}", + context + ) + self.assertHTMLEqual(rendered, "") + + def test_render_html_field_empty(self): + topic = self.test_data.create_topic(1) + curriculum_integration_empty = CurriculumIntegration.objects.create( + topic=topic, + slug="slug-3", + number=3, + name="3", + content="" + ) + context = {"integration": curriculum_integration_empty} + rendered = self.render_template( + "{% load render_html_field %}\n{% render_html_field integration.content %}", + context + ) + self.assertEqual(rendered.strip(), "") + + def test_render_html_field_zero_parameters(self): + self.assertRaises( + template.TemplateSyntaxError, + self.render_template, + "{% load render_html_field %}\n{% render_html_field %}" + ) + + def test_render_html_field_two_parameters(self): + self.assertRaises( + template.TemplateSyntaxError, + self.render_template, + "{% load render_html_field %}\n{% render_html_field param1 param2 %}" + ) + + def test_render_html_field_invalid_parameter(self): + topic = self.test_data.create_topic(1) + curriculum_integration_with_static = CurriculumIntegration.objects.create( + topic=topic, + slug="slug-2", + number=2, + name="2", + content="" + ) + context = {"integration": curriculum_integration_with_static} + self.assertRaises( + template.TemplateSyntaxError, + self.render_template, + "{% load render_html_field %}\n{% render_html_field integration.topic %}", + context + ) + + def test_render_html_field_missing_parameter(self): + topic = self.test_data.create_topic(1) + curriculum_integration_with_static = CurriculumIntegration.objects.create( + topic=topic, + slug="slug-2", + number=2, + name="2", + content="" + ) + context = {"integration": curriculum_integration_with_static} + self.assertRaises( + template.TemplateSyntaxError, + self.render_template, + "{% load render_html_field %}\n{% render_html_field integration.invalid %}", + context + ) diff --git a/csunplugged/tests/general/urls/__init__.py b/csunplugged/tests/general/urls/__init__.py new file mode 100644 index 000000000..106b86b8b --- /dev/null +++ b/csunplugged/tests/general/urls/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the URL routing in the general application.""" diff --git a/csunplugged/tests/general/urls/test_about_url.py b/csunplugged/tests/general/urls/test_about_url.py new file mode 100644 index 000000000..9f2d71cdc --- /dev/null +++ b/csunplugged/tests/general/urls/test_about_url.py @@ -0,0 +1,16 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class AboutURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_about_url(self): + url = reverse("general:about") + self.assertEqual(url, "/en/about/") + + response = self.client.get(url) + self.assertEqual(200, response.status_code) diff --git a/csunplugged/tests/general/urls/test_contact_url.py b/csunplugged/tests/general/urls/test_contact_url.py new file mode 100644 index 000000000..7a60524a7 --- /dev/null +++ b/csunplugged/tests/general/urls/test_contact_url.py @@ -0,0 +1,16 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class ContactURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_contact_url(self): + url = reverse("general:contact") + self.assertEqual(url, "/en/contact/") + + response = self.client.get(url) + self.assertEqual(200, response.status_code) diff --git a/csunplugged/tests/general/urls/test_health_check_url.py b/csunplugged/tests/general/urls/test_health_check_url.py new file mode 100644 index 000000000..6ad62856b --- /dev/null +++ b/csunplugged/tests/general/urls/test_health_check_url.py @@ -0,0 +1,12 @@ +from tests.BaseTestWithDB import BaseTestWithDB + + +class HealthCheckURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_health_check_request(self): + response = self.client.get("/_ah/health") + self.assertEqual(200, response.status_code) diff --git a/csunplugged/tests/general/urls/test_index_url.py b/csunplugged/tests/general/urls/test_index_url.py new file mode 100644 index 000000000..e395aea6a --- /dev/null +++ b/csunplugged/tests/general/urls/test_index_url.py @@ -0,0 +1,16 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class IndexURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_index_request(self): + url = reverse("general:home") + self.assertEqual(url, "/en/") + + response = self.client.get(url) + self.assertEqual(200, response.status_code) diff --git a/csunplugged/tests/general/urls/test_people_url.py b/csunplugged/tests/general/urls/test_people_url.py new file mode 100644 index 000000000..11fe2bf75 --- /dev/null +++ b/csunplugged/tests/general/urls/test_people_url.py @@ -0,0 +1,16 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class PeopleURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_people_url(self): + url = reverse("general:people") + self.assertEqual(url, "/en/people/") + + response = self.client.get(url) + self.assertEqual(200, response.status_code) diff --git a/csunplugged/tests/general/urls/test_principles_url.py b/csunplugged/tests/general/urls/test_principles_url.py new file mode 100644 index 000000000..d416d0fb6 --- /dev/null +++ b/csunplugged/tests/general/urls/test_principles_url.py @@ -0,0 +1,16 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class PrinciplesURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_principles_url(self): + url = reverse("general:principles") + self.assertEqual(url, "/en/principles/") + + response = self.client.get(url) + self.assertEqual(200, response.status_code) diff --git a/csunplugged/tests/general/views/__init__.py b/csunplugged/tests/general/views/__init__.py new file mode 100644 index 000000000..4d1ad08de --- /dev/null +++ b/csunplugged/tests/general/views/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the views in the general application.""" diff --git a/csunplugged/tests/general/views/test_general_about_view.py b/csunplugged/tests/general/views/test_general_about_view.py new file mode 100644 index 000000000..5a880e416 --- /dev/null +++ b/csunplugged/tests/general/views/test_general_about_view.py @@ -0,0 +1,14 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class AboutViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_about_view(self): + response = self.client.get(reverse("general:about")) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "About") diff --git a/csunplugged/tests/general/views/test_general_contact_view.py b/csunplugged/tests/general/views/test_general_contact_view.py new file mode 100644 index 000000000..9a4d4715b --- /dev/null +++ b/csunplugged/tests/general/views/test_general_contact_view.py @@ -0,0 +1,14 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class ContactViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_contact_view(self): + response = self.client.get(reverse("general:contact")) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Contact Us") diff --git a/csunplugged/tests/general/views/test_general_index_view.py b/csunplugged/tests/general/views/test_general_index_view.py new file mode 100644 index 000000000..3e73b4d17 --- /dev/null +++ b/csunplugged/tests/general/views/test_general_index_view.py @@ -0,0 +1,14 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class IndexViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_index_view(self): + response = self.client.get(reverse("general:home")) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Computer Science") diff --git a/csunplugged/tests/general/views/test_general_people_view.py b/csunplugged/tests/general/views/test_general_people_view.py new file mode 100644 index 000000000..46c8ece05 --- /dev/null +++ b/csunplugged/tests/general/views/test_general_people_view.py @@ -0,0 +1,14 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class PeopleViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_people_view(self): + response = self.client.get(reverse("general:people")) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "People") diff --git a/csunplugged/tests/general/views/test_general_principles_view.py b/csunplugged/tests/general/views/test_general_principles_view.py new file mode 100644 index 000000000..ec38ad784 --- /dev/null +++ b/csunplugged/tests/general/views/test_general_principles_view.py @@ -0,0 +1,14 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class PrinciplesViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_principles_view(self): + response = self.client.get(reverse("general:principles")) + self.assertEqual(response.status_code, 200) + self.assertContains(response, "Principles") diff --git a/csunplugged/tests/resources/ResourcesTestDataGenerator.py b/csunplugged/tests/resources/ResourcesTestDataGenerator.py new file mode 100644 index 000000000..acd3cf242 --- /dev/null +++ b/csunplugged/tests/resources/ResourcesTestDataGenerator.py @@ -0,0 +1,30 @@ +"""Create test data for resource tests.""" + +from resources.models import Resource + + +class ResourcesTestDataGenerator: + """Class for generating test data for resource tests.""" + + def create_resource(self, slug, name, webpage_template, generation_view): + """Create resource object. + + Args: + slug: Resource slug (str). + name: Resource name (str). + webpage_template: Path to HTML form template (str). + generation_view: Python view module filename (str). + + Returns: + Resource object. + """ + resource = Resource( + slug="resource-{}".format(slug), + name="Resource {}".format(name), + webpage_template=webpage_template, + generation_view=generation_view, + thumbnail_static_path="static/images/thumbnail-{}".format(slug), + copies=False, + ) + resource.save() + return resource diff --git a/csunplugged/tests/resources/__init__.py b/csunplugged/tests/resources/__init__.py new file mode 100644 index 000000000..dc3501825 --- /dev/null +++ b/csunplugged/tests/resources/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the resources application.""" diff --git a/csunplugged/tests/resources/models/__init__.py b/csunplugged/tests/resources/models/__init__.py new file mode 100644 index 000000000..1aca1c211 --- /dev/null +++ b/csunplugged/tests/resources/models/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the models in the resources application.""" diff --git a/csunplugged/tests/resources/models/test_resource.py b/csunplugged/tests/resources/models/test_resource.py new file mode 100644 index 000000000..e69de29bb diff --git a/csunplugged/tests/resources/resource/__init__.py b/csunplugged/tests/resources/resource/__init__.py new file mode 100644 index 000000000..58cd3f5db --- /dev/null +++ b/csunplugged/tests/resources/resource/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the resources in the resources application.""" diff --git a/csunplugged/tests/resources/resource/test_sorting_network.py b/csunplugged/tests/resources/resource/test_sorting_network.py new file mode 100644 index 000000000..e69de29bb diff --git a/csunplugged/tests/resources/resource/test_treasure_hunt.py b/csunplugged/tests/resources/resource/test_treasure_hunt.py new file mode 100644 index 000000000..e69de29bb diff --git a/csunplugged/tests/resources/urls/__init__.py b/csunplugged/tests/resources/urls/__init__.py new file mode 100644 index 000000000..5de2902b2 --- /dev/null +++ b/csunplugged/tests/resources/urls/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the URL routing in the resources application.""" diff --git a/csunplugged/tests/resources/urls/test_binary_cards.py b/csunplugged/tests/resources/urls/test_binary_cards.py new file mode 100644 index 000000000..22539afd5 --- /dev/null +++ b/csunplugged/tests/resources/urls/test_binary_cards.py @@ -0,0 +1,19 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class BinaryCardsResourceURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + self.RESOURCE_SLUG = "binary-cards" + self.RESOURCE_URL_KWARGS = {"resource_slug": self.RESOURCE_SLUG} + + def test_valid_binary_cards_resource_url(self): + url = reverse("resources:resource", kwargs=self.RESOURCE_URL_KWARGS) + self.assertEqual(url, "/en/resources/binary-cards/") + + def test_valid_binary_cards_resource_generate_url(self): + url = reverse("resources:generate", kwargs=self.RESOURCE_URL_KWARGS) + self.assertEqual(url, "/en/resources/binary-cards/generate") diff --git a/csunplugged/tests/resources/urls/test_binary_to_alphabet.py b/csunplugged/tests/resources/urls/test_binary_to_alphabet.py new file mode 100644 index 000000000..63ea98efb --- /dev/null +++ b/csunplugged/tests/resources/urls/test_binary_to_alphabet.py @@ -0,0 +1,19 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class BinaryToAlphabetResourceURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + self.RESOURCE_SLUG = "binary-to-alphabet" + self.RESOURCE_URL_KWARGS = {"resource_slug": self.RESOURCE_SLUG} + + def test_valid_binary_to_alphabet_resource_url(self): + url = reverse("resources:resource", kwargs=self.RESOURCE_URL_KWARGS) + self.assertEqual(url, "/en/resources/binary-to-alphabet/") + + def test_valid_binary_to_alphabet_resource_generate_url(self): + url = reverse("resources:generate", kwargs=self.RESOURCE_URL_KWARGS) + self.assertEqual(url, "/en/resources/binary-to-alphabet/generate") diff --git a/csunplugged/tests/resources/urls/test_binary_windows.py b/csunplugged/tests/resources/urls/test_binary_windows.py new file mode 100644 index 000000000..4b66c5b93 --- /dev/null +++ b/csunplugged/tests/resources/urls/test_binary_windows.py @@ -0,0 +1,19 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class BinaryWindowsResourceURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + self.RESOURCE_SLUG = "binary-windows" + self.RESOURCE_URL_KWARGS = {"resource_slug": self.RESOURCE_SLUG} + + def test_valid_binary_windows_resource_url(self): + url = reverse("resources:resource", kwargs=self.RESOURCE_URL_KWARGS) + self.assertEqual(url, "/en/resources/binary-windows/") + + def test_valid_binary_windows_resource_generate_url(self): + url = reverse("resources:generate", kwargs=self.RESOURCE_URL_KWARGS) + self.assertEqual(url, "/en/resources/binary-windows/generate") diff --git a/csunplugged/tests/resources/urls/test_index.py b/csunplugged/tests/resources/urls/test_index.py new file mode 100644 index 000000000..131879e40 --- /dev/null +++ b/csunplugged/tests/resources/urls/test_index.py @@ -0,0 +1,16 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class IndexURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_resources_index(self): + url = reverse("resources:index") + self.assertEqual(url, "/en/resources/") + + response = self.client.get(url) + self.assertEqual(200, response.status_code) diff --git a/csunplugged/tests/resources/urls/test_modulo_clock.py b/csunplugged/tests/resources/urls/test_modulo_clock.py new file mode 100644 index 000000000..80a749a79 --- /dev/null +++ b/csunplugged/tests/resources/urls/test_modulo_clock.py @@ -0,0 +1,19 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class ModuloClockResourceURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + self.RESOURCE_SLUG = "modulo-clock" + self.RESOURCE_URL_KWARGS = {"resource_slug": self.RESOURCE_SLUG} + + def test_valid_modulo_clock_resource_url(self): + url = reverse("resources:resource", kwargs=self.RESOURCE_URL_KWARGS) + self.assertEqual(url, "/en/resources/modulo-clock/") + + def test_valid_modulo_clock_resource_generate_url(self): + url = reverse("resources:generate", kwargs=self.RESOURCE_URL_KWARGS) + self.assertEqual(url, "/en/resources/modulo-clock/generate") diff --git a/csunplugged/tests/resources/urls/test_parity_cards.py b/csunplugged/tests/resources/urls/test_parity_cards.py new file mode 100644 index 000000000..9065c9a25 --- /dev/null +++ b/csunplugged/tests/resources/urls/test_parity_cards.py @@ -0,0 +1,19 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class ParityCardsResourceURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + self.RESOURCE_SLUG = "parity-cards" + self.RESOURCE_URL_KWARGS = {"resource_slug": self.RESOURCE_SLUG} + + def test_valid_parity_cards_resource_url(self): + url = reverse("resources:resource", kwargs=self.RESOURCE_URL_KWARGS) + self.assertEqual(url, "/en/resources/parity-cards/") + + def test_valid_parity_cards_resource_generate_url(self): + url = reverse("resources:generate", kwargs=self.RESOURCE_URL_KWARGS) + self.assertEqual(url, "/en/resources/parity-cards/generate") diff --git a/csunplugged/tests/resources/urls/test_resource.py b/csunplugged/tests/resources/urls/test_resource.py new file mode 100644 index 000000000..e69de29bb diff --git a/csunplugged/tests/resources/urls/test_sorting_network_cards.py b/csunplugged/tests/resources/urls/test_sorting_network_cards.py new file mode 100644 index 000000000..c608d625e --- /dev/null +++ b/csunplugged/tests/resources/urls/test_sorting_network_cards.py @@ -0,0 +1,19 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class SortingNetworkCardsResourceURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + self.RESOURCE_SLUG = "sorting-network-cards" + self.RESOURCE_URL_KWARGS = {"resource_slug": self.RESOURCE_SLUG} + + def test_valid_sorting_network_cards_resource_url(self): + url = reverse("resources:resource", kwargs=self.RESOURCE_URL_KWARGS) + self.assertEqual(url, "/en/resources/sorting-network-cards/") + + def test_valid_sorting_network_cards_resource_generate_url(self): + url = reverse("resources:generate", kwargs=self.RESOURCE_URL_KWARGS) + self.assertEqual(url, "/en/resources/sorting-network-cards/generate") diff --git a/csunplugged/tests/resources/views/__init__.py b/csunplugged/tests/resources/views/__init__.py new file mode 100644 index 000000000..49bda251a --- /dev/null +++ b/csunplugged/tests/resources/views/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the views in the resources application.""" diff --git a/csunplugged/tests/resources/views/test_binary_cards.py b/csunplugged/tests/resources/views/test_binary_cards.py new file mode 100644 index 000000000..e9a1a86f4 --- /dev/null +++ b/csunplugged/tests/resources/views/test_binary_cards.py @@ -0,0 +1,260 @@ +from django.urls import reverse +from tests.BaseTestWithDB import BaseTestWithDB +from tests.resources.ResourcesTestDataGenerator import ResourcesTestDataGenerator +from utils.create_query_string import query_string + + +class BinaryCardsResourceViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = ResourcesTestDataGenerator() + self.language = "en" + + def test_binary_cards_resource_form_view(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards", + "resources/binary-cards.html", + "binary_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:resource", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + + def test_binary_cards_resource_generation(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards", + "resources/binary-cards.html", + "binary_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "display_numbers": "yes", + "black_back": "no", + "paper_size": "a4", + "header_text": "", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary Cards (with numbers - without black back - a4).pdf"' + ) + + def test_binary_cards_resource_generation_no_numbers(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards", + "resources/binary-cards.html", + "binary_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "display_numbers": "no", + "black_back": "no", + "paper_size": "a4", + "header_text": "", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary Cards (without numbers - without black back - a4).pdf"' + ) + + def test_binary_cards_resource_generation_black_back(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards", + "resources/binary-cards.html", + "binary_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "display_numbers": "yes", + "black_back": "yes", + "paper_size": "a4", + "header_text": "", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary Cards (with numbers - with black back - a4).pdf"' + ) + + def test_binary_cards_resource_generation_no_numbers_black_back(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards", + "resources/binary-cards.html", + "binary_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "display_numbers": "no", + "black_back": "yes", + "paper_size": "a4", + "header_text": "", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary Cards (without numbers - with black back - a4).pdf"' + ) + + def test_binary_cards_resource_generation_us_letter(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards", + "resources/binary-cards.html", + "binary_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "display_numbers": "yes", + "black_back": "no", + "paper_size": "letter", + "header_text": "", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary Cards (with numbers - without black back - letter).pdf"' + ) + + def test_binary_cards_resource_generation_header_text(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards", + "resources/binary-cards.html", + "binary_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "display_numbers": "yes", + "black_back": "no", + "paper_size": "a4", + "header_text": "Example header text", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary Cards (with numbers - without black back - a4).pdf"' + ) + + def test_binary_cards_resource_generation_missing_numbers_parameter(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards", + "resources/binary-cards.html", + "binary_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "black_back": "no", + "paper_size": "a4", + "header_text": "Example header text", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_binary_cards_resource_generation_missing_back_parameter(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards", + "resources/binary-cards.html", + "binary_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "display_numbers": "yes", + "paper_size": "a4", + "header_text": "Example header text", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_binary_cards_resource_generation_missing_paper_size_parameter(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards", + "resources/binary-cards.html", + "binary_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "display_numbers": "yes", + "black_back": "no", + "header_text": "Example header text", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_binary_cards_resource_generation_missing_header_text_parameter(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards", + "resources/binary-cards.html", + "binary_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "display_numbers": "yes", + "black_back": "no", + "paper_size": "a4", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary Cards (with numbers - without black back - a4).pdf"' + ) diff --git a/csunplugged/tests/resources/views/test_binary_cards_small.py b/csunplugged/tests/resources/views/test_binary_cards_small.py new file mode 100644 index 000000000..605cdb382 --- /dev/null +++ b/csunplugged/tests/resources/views/test_binary_cards_small.py @@ -0,0 +1,188 @@ +import itertools +from django.urls import reverse +from tests.BaseTestWithDB import BaseTestWithDB +from tests.resources.ResourcesTestDataGenerator import ResourcesTestDataGenerator +from utils.import_resource_module import import_resource_module +from utils.create_query_string import query_string + + +class BinaryCardsSmallResourceViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = ResourcesTestDataGenerator() + self.language = "en" + + def test_binary_cards_small_resource_form_view(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards (small)", + "resources/binary-cards-small.html", + "binary_cards_small.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:resource", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + + def test_binary_cards_small_resource_generation_valid_configurations(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards (small)", + "resources/binary-cards-small.html", + "binary_cards_small.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + base_url = reverse("resources:generate", kwargs=kwargs) + resource_module = import_resource_module(resource) + valid_options = resource_module.valid_options() + valid_options["header_text"] = ["", "Example header"] + valid_option_keys = sorted(valid_options) + combinations = [dict(zip(valid_option_keys, product)) for product in itertools.product(*(valid_options[valid_option_key] for valid_option_key in valid_option_keys))] # noqa: E501 + print() + for combination in combinations: + print(" - Testing combination: {} ... ".format(combination), end="") + url_combination = {} + for parameter in combination: + if combination[parameter] is True: + url_combination[parameter] = "yes" + elif combination[parameter] is False: + url_combination[parameter] = "no" + else: + url_combination[parameter] = combination[parameter] + url = base_url + query_string(url_combination) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + if combination["dot_counts"]: + display_numbers_text = "with dot counts" + else: + display_numbers_text = "without dot counts" + if combination["black_back"]: + black_back_text = "with black back" + else: + black_back_text = "without black back" + subtitle = "{} bits - {} - {} - {}".format( + combination["number_bits"], + display_numbers_text, + black_back_text, + combination["paper_size"], + ) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary Cards (small) ({subtitle}).pdf"'.format(subtitle=subtitle) + ) + print("ok") + + def test_binary_cards_small_resource_generation_missing_dot_count_parameter(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards (small)", + "resources/binary-cards-small.html", + "binary_cards_small.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "number_bits": "4", + "black_back": "yes", + "paper_size": "a4", + "header_text": "", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_binary_cards_small_resource_generation_missing_number_bits_parameter(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards (small)", + "resources/binary-cards-small.html", + "binary_cards_small.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "dot_counts": "yes", + "black_back": "yes", + "paper_size": "a4", + "header_text": "", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_binary_cards_small_resource_generation_missing_black_back_parameter(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards (small)", + "resources/binary-cards-small.html", + "binary_cards_small.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "number_bits": "4", + "dot_counts": "yes", + "paper_size": "a4", + "header_text": "", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_binary_cards_small_resource_generation_missing_paper_size_parameter(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards (small)", + "resources/binary-cards-small.html", + "binary_cards_small.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "number_bits": "4", + "dot_counts": "yes", + "black_back": "yes", + "header_text": "", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_binary_cards_small_resource_generation_missing_header_text_parameter(self): + resource = self.test_data.create_resource( + "binary-cards", + "Binary Cards (small)", + "resources/binary-cards-small.html", + "binary_cards_small.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "dot_counts": "yes", + "number_bits": "4", + "black_back": "yes", + "paper_size": "a4", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + filename = "Resource Binary Cards (small) (4 bits - with dot counts - with black back - a4).pdf" + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="{}"'.format(filename) + ) diff --git a/csunplugged/tests/resources/views/test_binary_to_alphabet.py b/csunplugged/tests/resources/views/test_binary_to_alphabet.py new file mode 100644 index 000000000..694c2ffd1 --- /dev/null +++ b/csunplugged/tests/resources/views/test_binary_to_alphabet.py @@ -0,0 +1,279 @@ +from django.urls import reverse +from tests.BaseTestWithDB import BaseTestWithDB +from tests.resources.ResourcesTestDataGenerator import ResourcesTestDataGenerator +from utils.create_query_string import query_string + + +class BinaryToAlphabetResourceViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = ResourcesTestDataGenerator() + self.language = "en" + + def test_binary_to_alphabet_resource_form_view(self): + resource = self.test_data.create_resource( + "binary-to-alphabet", + "Binary To Alphabet", + "resources/binary-to-alphabet.html", + "binary_to_alphabet.py" + ) + kwargs = { + "resource_slug": resource.slug + } + url = reverse("resources:resource", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + + def test_binary_to_alphabet_resource_generation_student_a4_no_header_text(self): + resource = self.test_data.create_resource( + "binary-to-alphabet", + "Binary To Alphabet", + "resources/binary-to-alphabet.html", + "binary_to_alphabet.py" + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "worksheet_version": "student", + "paper_size": "a4", + "header_text": "" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary To Alphabet (student - a4).pdf"' + ) + + def test_binary_to_alphabet_resource_generation_student_letter_no_header_text(self): + resource = self.test_data.create_resource( + "binary-to-alphabet", + "Binary To Alphabet", + "resources/binary-to-alphabet.html", + "binary_to_alphabet.py" + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "worksheet_version": "student", + "paper_size": "letter", + "header_text": "" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary To Alphabet (student - letter).pdf"' + ) + + def test_binary_to_alphabet_resource_generation_teacher_a4_no_header_text(self): + resource = self.test_data.create_resource( + "binary-to-alphabet", + "Binary To Alphabet", + "resources/binary-to-alphabet.html", + "binary_to_alphabet.py" + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "worksheet_version": "teacher", + "paper_size": "a4", + "header_text": "" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary To Alphabet (teacher - a4).pdf"' + ) + + def test_binary_to_alphabet_resource_generation_teacher_letter_no_header_text(self): + resource = self.test_data.create_resource( + "binary-to-alphabet", + "Binary To Alphabet", + "resources/binary-to-alphabet.html", + "binary_to_alphabet.py" + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "worksheet_version": "teacher", + "paper_size": "letter", + "header_text": "" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary To Alphabet (teacher - letter).pdf"' + ) + + def test_binary_to_alphabet_resource_generation_student_a4_header_text(self): + resource = self.test_data.create_resource( + "binary-to-alphabet", + "Binary To Alphabet", + "resources/binary-to-alphabet.html", + "binary_to_alphabet.py" + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "worksheet_version": "student", + "paper_size": "a4", + "header_text": "Binary To Alphabet" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary To Alphabet (student - a4).pdf"' + ) + + def test_binary_to_alphabet_resource_generation_student_letter_header_text(self): + resource = self.test_data.create_resource( + "binary-to-alphabet", + "Binary To Alphabet", + "resources/binary-to-alphabet.html", + "binary_to_alphabet.py" + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "worksheet_version": "student", + "paper_size": "letter", + "header_text": "Binary To Alphabet" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary To Alphabet (student - letter).pdf"' + ) + + def test_binary_to_alphabet_resource_generation_teacher_a4_header_text(self): + resource = self.test_data.create_resource( + "binary-to-alphabet", + "Binary To Alphabet", + "resources/binary-to-alphabet.html", + "binary_to_alphabet.py" + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "worksheet_version": "teacher", + "paper_size": "a4", + "header_text": "Binary To Alphabet" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary To Alphabet (teacher - a4).pdf"' + ) + + def test_binary_to_alphabet_resource_generation_teacher_letter_header_text(self): + resource = self.test_data.create_resource( + "binary-to-alphabet", + "Binary To Alphabet", + "resources/binary-to-alphabet.html", + "binary_to_alphabet.py" + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "worksheet_version": "teacher", + "paper_size": "letter", + "header_text": "Binary To Alphabet" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary To Alphabet (teacher - letter).pdf"' + ) + + def test_binary_to_alphabet_resource_generation_missing_header_text_parameter(self): + resource = self.test_data.create_resource( + "binary-to-alphabet", + "Binary To Alphabet", + "resources/binary-to-alphabet.html", + "binary_to_alphabet.py" + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "worksheet_version": "teacher", + "paper_size": "letter", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary To Alphabet (teacher - letter).pdf"' + ) + + def test_binary_to_alphabet_resource_generation_missing_worksheet_version_parameter(self): + resource = self.test_data.create_resource( + "binary-to-alphabet", + "Binary To Alphabet", + "resources/binary-to-alphabet.html", + "binary_to_alphabet.py" + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "paper_size": "letter", + "header_text": "" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_binary_to_alphabet_resource_generation_missing_paper_size_parameter(self): + resource = self.test_data.create_resource( + "binary-to-alphabet", + "Binary To Alphabet", + "resources/binary-to-alphabet.html", + "binary_to_alphabet.py" + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "worksheet_version": "student", + "header_text": "" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) diff --git a/csunplugged/tests/resources/views/test_binary_windows.py b/csunplugged/tests/resources/views/test_binary_windows.py new file mode 100644 index 000000000..7f3729c1f --- /dev/null +++ b/csunplugged/tests/resources/views/test_binary_windows.py @@ -0,0 +1,180 @@ +import itertools +from django.urls import reverse +from tests.BaseTestWithDB import BaseTestWithDB +from tests.resources.ResourcesTestDataGenerator import ResourcesTestDataGenerator +from utils.create_query_string import query_string + + +class BinaryWindowsResourceViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = ResourcesTestDataGenerator() + self.language = "en" + + def test_binary_windows_resource_form_view(self): + resource = self.test_data.create_resource( + "binary-windows", + "Binary Windows", + "resources/binary-windows.html", + "binary_windows.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:resource", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + + def test_binary_windows_resource_generation_valid_configurations(self): + resource = self.test_data.create_resource( + "binary-windows", + "Binary Windows", + "resources/binary-windows.html", + "binary_windows.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + base_url = reverse("resources:generate", kwargs=kwargs) + + valid_options = { + "number_bits": ["4", "8"], + "value_type": ["binary", "lightbulb"], + "dot_counts": ["yes", "no"], + "paper_size": ["a4", "letter"], + "header_text": ["", "Example header"], + } + valid_option_keys = sorted(valid_options) + combinations = [dict(zip(valid_option_keys, product)) for product in itertools.product(*(valid_options[valid_option_key] for valid_option_key in valid_option_keys))] # noqa: E501 + print() + for combination in combinations: + print(" - Testing combination: {} ... ".format(combination), end="") + url = base_url + query_string(combination) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + if combination["dot_counts"] == "yes": + count_text = "with dot counts" + else: + count_text = "without dot counts" + TEMPLATE = "{num_bits} bits - {value} - {counts}" + subtitle = TEMPLATE.format( + num_bits=combination["number_bits"], + value=combination["value_type"], + counts=count_text + ) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary Windows ({subtitle}).pdf"'.format(subtitle=subtitle) + ) + print("ok") + + def test_binary_windows_resource_generation_missing_dot_count_parameter(self): + resource = self.test_data.create_resource( + "binary-windows", + "Binary Windows", + "resources/binary-windows.html", + "binary_windows.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "number_bits": "8", + "value_type": "binary", + "paper_size": "a4", + "header_text": "Example header text", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_binary_windows_resource_generation_missing_number_bits_parameter(self): + resource = self.test_data.create_resource( + "binary-windows", + "Binary Windows", + "resources/binary-windows.html", + "binary_windows.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "dot_counts": "yes", + "value_type": "binary", + "paper_size": "a4", + "header_text": "Example header text", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_binary_windows_resource_generation_missing_value_type_parameter(self): + resource = self.test_data.create_resource( + "binary-windows", + "Binary Windows", + "resources/binary-windows.html", + "binary_windows.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "dot_counts": "yes", + "number_bits": "8", + "paper_size": "a4", + "header_text": "Example header text", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_binary_windows_resource_generation_missing_paper_size_parameter(self): + resource = self.test_data.create_resource( + "binary-windows", + "Binary Windows", + "resources/binary-windows.html", + "binary_windows.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "dot_counts": "yes", + "number_bits": "8", + "value_type": "binary", + "header_text": "Example header text", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_binary_windows_resource_generation_missing_header_text_parameter(self): + resource = self.test_data.create_resource( + "binary-windows", + "Binary Windows", + "resources/binary-windows.html", + "binary_windows.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "dot_counts": "yes", + "number_bits": "8", + "value_type": "lightbulb", + "paper_size": "a4", + "header_text": "", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Binary Windows (8 bits - lightbulb - with dot counts).pdf"' + ) diff --git a/csunplugged/tests/resources/views/test_index_view.py b/csunplugged/tests/resources/views/test_index_view.py new file mode 100644 index 000000000..86f553d2d --- /dev/null +++ b/csunplugged/tests/resources/views/test_index_view.py @@ -0,0 +1,56 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse +from tests.resources.ResourcesTestDataGenerator import ResourcesTestDataGenerator + + +class IndexViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = ResourcesTestDataGenerator() + self.language = "en" + + def test_resources_index_with_no_resources(self): + url = reverse("resources:index") + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertFalse(response.context["all_resources"].exists()) + + def test_resources_index_with_one_resource(self): + self.test_data.create_resource( + "binary-cards", + "Binary Cards", + "resources/binary-cards.html", + "binary_cards.py", + ) + url = reverse("resources:index") + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertQuerysetEqual( + response.context["all_resources"], + [""] + ) + + def test_resources_index_with_multiple_resources(self): + self.test_data.create_resource( + "binary-cards", + "Binary Cards", + "resources/binary-cards.html", + "binary_cards.py", + ) + self.test_data.create_resource( + "sorting-network", + "Sorting Network", + "resources/sorting-network.html", + "sorting_network.py", + ) + url = reverse("resources:index") + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertQuerysetEqual( + response.context["all_resources"], + [ + "", + "", + ] + ) diff --git a/csunplugged/tests/resources/views/test_modulo_clock.py b/csunplugged/tests/resources/views/test_modulo_clock.py new file mode 100644 index 000000000..2534bbfe0 --- /dev/null +++ b/csunplugged/tests/resources/views/test_modulo_clock.py @@ -0,0 +1,290 @@ +from django.urls import reverse + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.resources.ResourcesTestDataGenerator import ResourcesTestDataGenerator + +from utils.create_query_string import query_string + + +class ModuloClockResourceViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = ResourcesTestDataGenerator() + self.language = "en" + + def test_modulo_clock_resource_form_view(self): + resource = self.test_data.create_resource( + "modulo-clock", + "Modulo Clock", + "resources/modulo-clock.html", + "modulo_clock.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:resource", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + + def test_modulo_clock_resource_generation_2_a4_no_header_text(self): + resource = self.test_data.create_resource( + "modulo-clock", + "Modulo Clock", + "resources/modulo-clock.html", + "modulo_clock.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "modulo_number": "2", + "paper_size": "a4", + "header_text": "" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + filename = "Resource Modulo Clock (2 - a4).pdf" + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="{}"'.format(filename) + ) + + def test_modulo_clock_resource_generation_2_letter_no_header_text(self): + resource = self.test_data.create_resource( + "modulo-clock", + "Modulo Clock", + "resources/modulo-clock.html", + "modulo_clock.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "modulo_number": "2", + "paper_size": "letter", + "header_text": "" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + filename = "Resource Modulo Clock (2 - letter).pdf" + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="{}"'.format(filename) + ) + + def test_modulo_clock_resource_generation_10_a4_no_header_text(self): + resource = self.test_data.create_resource( + "modulo-clock", + "Modulo Clock", + "resources/modulo-clock.html", + "modulo_clock.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "modulo_number": "10", + "paper_size": "a4", + "header_text": "" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + filename = "Resource Modulo Clock (10 - a4).pdf" + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="{}"'.format(filename) + ) + + def test_modulo_clock_resource_generation_10_letter_no_header_text(self): + resource = self.test_data.create_resource( + "modulo-clock", + "Modulo Clock", + "resources/modulo-clock.html", + "modulo_clock.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "modulo_number": "10", + "paper_size": "letter", + "header_text": "" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + filename = "Resource Modulo Clock (10 - letter).pdf" + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="{}"'.format(filename) + ) + + def test_modulo_clock_resource_generation_2_a4_header_text(self): + resource = self.test_data.create_resource( + "modulo-clock", + "Modulo Clock", + "resources/modulo-clock.html", + "modulo_clock.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "modulo_number": "2", + "paper_size": "a4", + "header_text": "Modulo Clock" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + filename = "Resource Modulo Clock (2 - a4).pdf" + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="{}"'.format(filename) + ) + + def test_modulo_clock_resource_generation_2_letter_header_text(self): + resource = self.test_data.create_resource( + "modulo-clock", + "Modulo Clock", + "resources/modulo-clock.html", + "modulo_clock.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "modulo_number": "2", + "paper_size": "letter", + "header_text": "" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + filename = "Resource Modulo Clock (2 - letter).pdf" + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="{}"'.format(filename) + ) + + def test_modulo_clock_resource_generation_10_a4_header_text(self): + resource = self.test_data.create_resource( + "modulo-clock", + "Modulo Clock", + "resources/modulo-clock.html", + "modulo_clock.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "modulo_number": "10", + "paper_size": "a4", + "header_text": "Modulo Clock" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + filename = "Resource Modulo Clock (10 - a4).pdf" + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="{}"'.format(filename) + ) + + def test_modulo_clock_resource_generation_10_letter_header_text(self): + resource = self.test_data.create_resource( + "modulo-clock", + "Modulo Clock", + "resources/modulo-clock.html", + "modulo_clock.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "modulo_number": "10", + "paper_size": "letter", + "header_text": "Modulo Clock" + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + filename = "Resource Modulo Clock (10 - letter).pdf" + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="{}"'.format(filename) + ) + + def test_modulo_clock_resource_generation_missing_modulo_number_parameter(self): + resource = self.test_data.create_resource( + "modulo-clock", + "Modulo Clock", + "resources/modulo-clock.html", + "modulo_clock.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "paper_size": "a4", + "header_text": "", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_modulo_clock_resource_generation_missing_paper_size_parameter(self): + resource = self.test_data.create_resource( + "modulo-clock", + "Modulo Clock", + "resources/modulo-clock.html", + "modulo_clock.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "modulo_number": "2", + "header_text": "", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_modulo_clock_resource_generation_missing_header_text_parameter(self): + resource = self.test_data.create_resource( + "modulo-clock", + "Modulo Clock", + "resources/modulo-clock.html", + "modulo_clock.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "modulo_number": "2", + "paper_size": "a4", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + filename = "Resource Modulo Clock (2 - a4).pdf" + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="{}"'.format(filename) + ) diff --git a/csunplugged/tests/resources/views/test_parity_cards.py b/csunplugged/tests/resources/views/test_parity_cards.py new file mode 100644 index 000000000..c50b88a60 --- /dev/null +++ b/csunplugged/tests/resources/views/test_parity_cards.py @@ -0,0 +1,120 @@ +from django.urls import reverse +from tests.BaseTestWithDB import BaseTestWithDB +from tests.resources.ResourcesTestDataGenerator import ResourcesTestDataGenerator +from utils.import_resource_module import import_resource_module +from utils.create_query_string import query_string +from utils.resource_valid_test_configurations import resource_valid_test_configurations + + +class BinaryCardsSmallResourceViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = ResourcesTestDataGenerator() + self.language = "en" + + def test_parity_cards_resource_form_view(self): + resource = self.test_data.create_resource( + "parity-cards", + "Parity Cards", + "resources/parity-cards.html", + "parity_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:resource", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + + def test_parity_cards_resource_generation_valid_configurations(self): + resource = self.test_data.create_resource( + "parity-cards", + "Parity Cards", + "resources/parity-cards.html", + "parity_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + base_url = reverse("resources:generate", kwargs=kwargs) + resource_module = import_resource_module(resource) + valid_options = resource_module.valid_options() + combinations = resource_valid_test_configurations(valid_options) + print() + for combination in combinations: + print(" - Testing combination: {} ... ".format(combination), end="") + url = base_url + query_string(combination) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + subtitle = "{} back - {}".format( + combination["back_colour"], + combination["paper_size"], + ) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Parity Cards ({subtitle}).pdf"'.format(subtitle=subtitle) + ) + print("ok") + + def test_parity_cards_resource_generation_missing_back_colour_parameter(self): + resource = self.test_data.create_resource( + "parity-cards", + "Parity Cards", + "resources/parity-cards.html", + "parity_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "paper_size": "a4", + "header_text": "", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_binary_cards_small_resource_generation_missing_paper_size_parameter(self): + resource = self.test_data.create_resource( + "parity-cards", + "Parity Cards", + "resources/parity-cards.html", + "parity_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "back_colour": "black", + "header_text": "", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_binary_cards_small_resource_generation_missing_header_text_parameter(self): + resource = self.test_data.create_resource( + "parity-cards", + "Parity Cards", + "resources/parity-cards.html", + "parity_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "back_colour": "black", + "paper_size": "a4", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + filename = "Resource Parity Cards (black back - a4).pdf" + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="{}"'.format(filename) + ) diff --git a/csunplugged/tests/resources/views/test_sorting_network_cards.py b/csunplugged/tests/resources/views/test_sorting_network_cards.py new file mode 100644 index 000000000..90c1e51e2 --- /dev/null +++ b/csunplugged/tests/resources/views/test_sorting_network_cards.py @@ -0,0 +1,122 @@ +import itertools +from django.urls import reverse +from tests.BaseTestWithDB import BaseTestWithDB +from tests.resources.ResourcesTestDataGenerator import ResourcesTestDataGenerator +from utils.import_resource_module import import_resource_module +from utils.create_query_string import query_string + + +class SortingNetworkCardsResourceViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = ResourcesTestDataGenerator() + self.language = "en" + + def test_sorting_network_cards_resource_form_view(self): + resource = self.test_data.create_resource( + "sorting-network-cards", + "Sorting Network Cards", + "resources/sorting-network-cards.html", + "sorting_network_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:resource", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + + def test_sorting_network_cards_resource_generation_valid_configurations(self): + resource = self.test_data.create_resource( + "sorting-network-cards", + "Sorting Network Cards", + "resources/sorting-network-cards.html", + "sorting_network_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + base_url = reverse("resources:generate", kwargs=kwargs) + resource_module = import_resource_module(resource) + valid_options = resource_module.valid_options() + valid_options["header_text"] = ["", "Example header"] + valid_option_keys = sorted(valid_options) + combinations = [dict(zip(valid_option_keys, product)) for product in itertools.product(*(valid_options[valid_option_key] for valid_option_key in valid_option_keys))] # noqa: E501 + print() + for combination in combinations: + print(" - Testing combination: {} ... ".format(combination), end="") + url = base_url + query_string(combination) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + subtitle = "{} - {}".format( + combination["type"].replace("_", " "), + combination["paper_size"], + ) + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="Resource Sorting Network Cards ({subtitle}).pdf"'.format(subtitle=subtitle) + ) + print("ok") + + def test_sorting_network_cards_resource_generation_missing_type_parameter(self): + resource = self.test_data.create_resource( + "sorting-network-cards", + "Sorting Network Cards", + "resources/sorting-network-cards.html", + "sorting_network_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "paper_size": "a4", + "header_text": "", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_sorting_network_cards_resource_generation_missing_paper_size_parameter(self): + resource = self.test_data.create_resource( + "sorting-network-cards", + "Sorting Network Cards", + "resources/sorting-network-cards.html", + "sorting_network_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "type": "small_numbers", + "header_text": "", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_sorting_network_cards_resource_generation_missing_header_text_parameter(self): + resource = self.test_data.create_resource( + "sorting-network-cards", + "Sorting Network Cards", + "resources/sorting-network-cards.html", + "sorting_network_cards.py", + ) + kwargs = { + "resource_slug": resource.slug, + } + url = reverse("resources:generate", kwargs=kwargs) + get_parameters = { + "type": "small_numbers", + "paper_size": "a4", + } + url += query_string(get_parameters) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + filename = "Resource Sorting Network Cards (small numbers - a4).pdf" + self.assertEqual( + response.get("Content-Disposition"), + 'attachment; filename="{}"'.format(filename) + ) diff --git a/csunplugged/tests/resources/views/test_view_methods.py b/csunplugged/tests/resources/views/test_view_methods.py new file mode 100644 index 000000000..e69de29bb diff --git a/csunplugged/tests/topics/TopicsTestDataGenerator.py b/csunplugged/tests/topics/TopicsTestDataGenerator.py new file mode 100644 index 000000000..880394501 --- /dev/null +++ b/csunplugged/tests/topics/TopicsTestDataGenerator.py @@ -0,0 +1,312 @@ +"""Create test data for topic tests.""" + +import os.path +import yaml + +from topics.models import ( + Topic, + UnitPlan, + Lesson, + LessonNumber, + AgeGroup, + CurriculumIntegration, + CurriculumArea, + ProgrammingChallenge, + ProgrammingChallengeDifficulty, + ProgrammingChallengeLanguage, + ProgrammingChallengeImplementation, + ProgrammingChallengeNumber, + LearningOutcome, +) + + +class TopicsTestDataGenerator: + """Class for generating test data for topics.""" + + def __init__(self): + """Create TopicsTestDataGenerator object.""" + self.BASE_PATH = "tests/topics/" + self.LOADER_ASSET_PATH = os.path.join(self.BASE_PATH, "loaders/assets/") + + def load_yaml_file(self, yaml_file_path): + """Load a yaml file. + + Args: + yaml_file_path: The path to a given yaml file (str). + + Returns: + Contents of a yaml file. + """ + yaml_file = open(yaml_file_path, encoding="UTF-8").read() + return yaml.load(yaml_file) + + def create_integration(self, topic, number, lessons=None, curriculum_areas=None): + """Create curriculum integration object. + + Args: + topic: The related Topic object (Topic). + number: Identifier of the topic (int). + lessons: List of prerequisite lessons (list). + curriculum_areas: List of curriculum areas (list). + + Returns: + CurriculumIntegration object. + """ + integration = CurriculumIntegration( + topic=topic, + slug="integration-{}".format(number), + name="Integration {}".format(number), + number=number, + content="

Content for integration {}.

".format(number), + ) + integration.save() + if lessons: + for lesson in lessons: + integration.prerequisite_lessons.add(lesson) + if curriculum_areas: + for curriculum_area in curriculum_areas: + integration.curriculum_areas.add(curriculum_area) + return integration + + def create_curriculum_area(self, number, parent=None): + """Create curriculum area object. + + Args: + number: Identifier of the area (int). + parent: Parent of the curriculum area (CurriculumArea). + + Returns: + CurriculumArea object. + """ + area = CurriculumArea( + slug="area-{}".format(number), + name="Area {}".format(number), + number=number, + parent=parent, + ) + area.save() + return area + + def create_topic(self, number): + """Create topic object. + + Args: + number: Identifier of the topic (int). + + Returns: + Topic object. + """ + topic = Topic( + slug="topic-{}".format(number), + name="Topic {}".format(number), + content="

Content for topic {}.

".format(number), + ) + topic.save() + return topic + + def create_unit_plan(self, topic, number): + """Create unit plan object. + + Args: + topic: The related Topic object (Topic). + number: Identifier of the unit plan (int). + + Returns: + UnitPlan object. + """ + unit_plan = UnitPlan( + topic=topic, + slug="unit-plan-{}".format(number), + name="Unit Plan {}".format(number), + content="

Content for unit plan {}.

".format(number), + ) + unit_plan.save() + return unit_plan + + def create_lesson(self, topic, unit_plan, number, age_group=None): + """Create lesson object. + + Args: + topic: The related Topic object (Topic). + unit_plan: The related UnitPlan object (UnitPlan). + number: Identifier of the topic (int). + + Returns: + Lesson object. + """ + lesson = Lesson( + topic=topic, + unit_plan=unit_plan, + slug="lesson-{}".format(number), + name="Lesson {} ({} to {})".format( + number, + age_group.ages[0] if age_group else "none", + age_group.ages[1] if age_group else "none" + ), + duration=number, + content="

Content for lesson {}.

".format(number), + ) + lesson.save() + if age_group: + LessonNumber( + age_group=age_group, + lesson=lesson, + number=number, + ).save() + return lesson + + def create_age_group(self, min_age, max_age): + """Create AgeGroup object. + + Args: + min_age: the minumum age for the group (int). + max_age: the maximum age for the group (int). + + Returns: + AgeGroup object. + """ + age_group = AgeGroup( + slug="{}-{}".format(min_age, max_age), + ages=(min_age, max_age) + ) + age_group.save() + return age_group + + def create_difficulty_level(self, number): + """Create difficuly level object. + + Args: + number: Identifier of the level (int). + + Returns: + ProgrammingChallengeDifficulty object. + """ + difficulty = ProgrammingChallengeDifficulty( + level="1", + name="Difficulty-{}".format(number) + ) + difficulty.save() + return difficulty + + def create_programming_language(self, number): + """Create programming language object. + + Args: + number: Identifier of the language (int). + + Returns: + ProgrammingChallengeLanguage object. + """ + language = ProgrammingChallengeLanguage( + slug="language-{}".format(number), + name="Language {}".format(number), + number=number, + ) + language.save() + return language + + def create_programming_challenge(self, topic, number, + difficulty, + challenge_set_number=1, + challenge_number=1, + content="

Example content.

", + extra_challenge="

Example challenge.

", + ): + """Create programming challenge object. + + Args: + topic: Topic related to the challenge. + number: Identifier of the challenge (int). + difficulty: Difficulty related to the challenge + (ProgrammingChallengeDifficulty). + challenge_set_number: Integer of challenge set number (int). + challenge_number: Integer of challenge number (int). + content: Text of challenge (str). + extra_challenge: Text of extra challenge (str). + + Returns: + ProgrammingChallenge object. + """ + challenge = ProgrammingChallenge( + topic=topic, + slug="challenge-{}".format(number), + name="Challenge {}.{}: {}".format( + challenge_set_number, + challenge_number, + number, + ), + challenge_set_number=challenge_set_number, + challenge_number=challenge_number, + content=content, + extra_challenge=extra_challenge, + difficulty=difficulty, + ) + challenge.save() + return challenge + + def create_programming_challenge_implementation(self, topic, + language, + challenge, + expected_result="

Example result.

", + hints="

Example hints.

", + solution="

Example solution.

", + ): + """Create programming challenge implementation object. + + Args: + topic: Topic related to the implementation. + language: Language related to the implementation + (ProgrammingChallengeLanguage). + challenge: Challenge related to the implementation + (ProgrammingChallenge). + expected_result: Text of expected_result (str). + hints: Text of hints (str). + solution: Text of solution (str). + + Returns: + ProgrammingChallengeImplementation object. + """ + implementation = ProgrammingChallengeImplementation( + topic=topic, + language=language, + challenge=challenge, + expected_result=expected_result, + hints=hints, + solution=solution, + ) + implementation.save() + return implementation + + def create_learning_outcome(self, number): + """Create learning outcome object. + + Args: + number: Identifier of the challenge (int). + + Returns: + LearningOutcome object. + """ + outcome = LearningOutcome( + slug="outcome-{}".format(number), + text="Outcome {}".format(number), + ) + outcome.save() + return outcome + + def add_challenge_lesson_relationship(self, challenge, lesson, set_number, number): + """Add relationship between challenge and lesson objects. + + Args: + challenge: Challenge to add relationship between + (ProgrammingChallenge). + lesson: Lesson to add relationship between (Lesson). + set_number: Number to display as challenge set number (int). + number: Number to display as challenge number (int). + """ + relationship = ProgrammingChallengeNumber( + programming_challenge=challenge, + lesson=lesson, + challenge_set_number=set_number, + challenge_number=number, + ) + relationship.save() diff --git a/csunplugged/tests/topics/__init__.py b/csunplugged/tests/topics/__init__.py new file mode 100644 index 000000000..7dab0ddcc --- /dev/null +++ b/csunplugged/tests/topics/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the topics application.""" diff --git a/csunplugged/tests/topics/loaders/__init__.py b/csunplugged/tests/topics/loaders/__init__.py new file mode 100644 index 000000000..c574fa5dc --- /dev/null +++ b/csunplugged/tests/topics/loaders/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the loaders in the topics application.""" diff --git a/csunplugged/tests/topics/loaders/assets/age_groups/basic-config.yaml b/csunplugged/tests/topics/loaders/assets/age_groups/basic-config.yaml new file mode 100644 index 000000000..12e195864 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/age_groups/basic-config.yaml @@ -0,0 +1,3 @@ +8-10: + min_age: 8 + max_age: 10 diff --git a/csunplugged/tests/topics/loaders/assets/age_groups/description.yaml b/csunplugged/tests/topics/loaders/assets/age_groups/description.yaml new file mode 100644 index 000000000..58608545c --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/age_groups/description.yaml @@ -0,0 +1,4 @@ +5-7: + min_age: 5 + max_age: 7 + description: Sample description. diff --git a/csunplugged/tests/topics/loaders/assets/age_groups/empty.yaml b/csunplugged/tests/topics/loaders/assets/age_groups/empty.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/csunplugged/tests/topics/loaders/assets/age_groups/missing-max-age.yaml b/csunplugged/tests/topics/loaders/assets/age_groups/missing-max-age.yaml new file mode 100644 index 000000000..a4c795cd0 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/age_groups/missing-max-age.yaml @@ -0,0 +1,2 @@ +8-10: + min_age: 8 diff --git a/csunplugged/tests/topics/loaders/assets/age_groups/missing-min-age.yaml b/csunplugged/tests/topics/loaders/assets/age_groups/missing-min-age.yaml new file mode 100644 index 000000000..0a1b01386 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/age_groups/missing-min-age.yaml @@ -0,0 +1,2 @@ +8-10: + max_age: 10 diff --git a/csunplugged/tests/topics/loaders/assets/age_groups/multiple.yaml b/csunplugged/tests/topics/loaders/assets/age_groups/multiple.yaml new file mode 100644 index 000000000..f4708d201 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/age_groups/multiple.yaml @@ -0,0 +1,10 @@ +5-7: + min_age: 5 + max_age: 7 + description: In the teacher observations sections there may also be background notes on the big picture. There is no expectation that 5 to 7 year olds will need to know this, but if you are asked, you have the answer at your fingertips. +8-10: + min_age: 8 + max_age: 10 +11-14: + min_age: 11 + max_age: 14 diff --git a/csunplugged/tests/topics/loaders/assets/curriculum_areas/basic-config.yaml b/csunplugged/tests/topics/loaders/assets/curriculum_areas/basic-config.yaml new file mode 100644 index 000000000..33aa1aad5 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/curriculum_areas/basic-config.yaml @@ -0,0 +1,4 @@ +maths: + name: Maths + colour: blue + number: 1 diff --git a/csunplugged/tests/topics/loaders/assets/curriculum_areas/children.yaml b/csunplugged/tests/topics/loaders/assets/curriculum_areas/children.yaml new file mode 100644 index 000000000..3ea629ccb --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/curriculum_areas/children.yaml @@ -0,0 +1,9 @@ +maths: + name: Maths + colour: blue + number: 1 + children: + geometry: + name: Geometry + algebra: + name: Algebra diff --git a/csunplugged/tests/topics/loaders/assets/curriculum_areas/empty-children.yaml b/csunplugged/tests/topics/loaders/assets/curriculum_areas/empty-children.yaml new file mode 100644 index 000000000..d1624179e --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/curriculum_areas/empty-children.yaml @@ -0,0 +1,5 @@ +maths: + name: Maths + colour: blue + number: 1 + children: diff --git a/csunplugged/tests/topics/loaders/assets/curriculum_areas/empty.yaml b/csunplugged/tests/topics/loaders/assets/curriculum_areas/empty.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/csunplugged/tests/topics/loaders/assets/curriculum_areas/missing-children-name.yaml b/csunplugged/tests/topics/loaders/assets/curriculum_areas/missing-children-name.yaml new file mode 100644 index 000000000..84676bcb2 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/curriculum_areas/missing-children-name.yaml @@ -0,0 +1,8 @@ +maths: + name: Maths + colour: blue + number: 1 + children: + geometry: + name: Geometry + algebra: diff --git a/csunplugged/tests/topics/loaders/assets/curriculum_areas/missing-colour.yaml b/csunplugged/tests/topics/loaders/assets/curriculum_areas/missing-colour.yaml new file mode 100644 index 000000000..50d8ea8a6 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/curriculum_areas/missing-colour.yaml @@ -0,0 +1,3 @@ +math: + name: Math + number: 1 diff --git a/csunplugged/tests/topics/loaders/assets/curriculum_areas/missing-name.yaml b/csunplugged/tests/topics/loaders/assets/curriculum_areas/missing-name.yaml new file mode 100644 index 000000000..20fc7d463 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/curriculum_areas/missing-name.yaml @@ -0,0 +1,3 @@ +math: + colour: blue + number: 1 diff --git a/csunplugged/tests/topics/loaders/assets/curriculum_areas/missing-number.yaml b/csunplugged/tests/topics/loaders/assets/curriculum_areas/missing-number.yaml new file mode 100644 index 000000000..b152acb74 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/curriculum_areas/missing-number.yaml @@ -0,0 +1,3 @@ +math: + name: Math + colour: blue diff --git a/csunplugged/tests/topics/loaders/assets/curriculum_areas/multiple.yaml b/csunplugged/tests/topics/loaders/assets/curriculum_areas/multiple.yaml new file mode 100644 index 000000000..eeac9a0dc --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/curriculum_areas/multiple.yaml @@ -0,0 +1,19 @@ +maths: + name: Maths + colour: green + number: 1 + children: + geometry: + name: Geometry + algebra: + name: Algebra + +science: + name: Science + colour: blue + number: 2 + +art: + name: Art + colour: teal + number: 3 diff --git a/csunplugged/tests/topics/loaders/assets/curriculum_integrations/basic-config.yaml b/csunplugged/tests/topics/loaders/assets/curriculum_integrations/basic-config.yaml new file mode 100644 index 000000000..b09e0c6be --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/curriculum_integrations/basic-config.yaml @@ -0,0 +1,4 @@ +integration-1: + number: 1 + curriculum-areas: + - area-1 diff --git a/csunplugged/tests/topics/loaders/assets/curriculum_integrations/integration-1.md b/csunplugged/tests/topics/loaders/assets/curriculum_integrations/integration-1.md new file mode 100644 index 000000000..fc0545df2 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/curriculum_integrations/integration-1.md @@ -0,0 +1,56 @@ +# Integration 1 + +Etiam in massa. Nam ut metus. In rhoncus venenatis tellus. Etiam aliquam. Ut +aliquam lectus ut lectus. Nam turpis lacus, tristique sit amet, convallis +sollicitudin, commodo a, purus. Nulla vitae eros a diam blandit mollis. Proin +luctus feugiat eros. Pellentesque habitant morbi tristique senectus et netus et +malesuada fames ac turpis egestas. Duis ultricies urna. Etiam enim urna, +pharetra suscipit, varius et, congue quis, odio. Donec lobortis, elit bibendum +euismod faucibus, velit nibh egestas libero, vitae pellentesque elit augue ut +massa. + +Nulla consequat erat at massa. Vivamus id mi. Morbi purus enim, dapibus a, +facilisis non, tincidunt at, enim. Vestibulum ante ipsum primis in faucibus +orci luctus et ultrices posuere cubilia Curae; Duis imperdiet eleifend arcu. +Cras magna ligula, consequat at, tempor non, posuere nec, libero. Vestibulum +vel ipsum. Praesent congue justo et nunc. Vestibulum nec felis vitae nisl +pharetra sollicitudin. Quisque nec arcu vel tellus tristique vestibulum. Aenean +vel lacus. Mauris dolor erat, commodo ut, dapibus vehicula, lobortis sit amet, +orci. Aliquam augue. In semper nisi nec libero. Cras magna ipsum, scelerisque +et, tempor eget, gravida nec, lacus. Fusce eros nisi, ullamcorper blandit, +ultricies eget, elementum eget, pede. Phasellus id risus vitae nisl ullamcorper +congue. Proin est. + +Sed eleifend odio sed leo. Mauris tortor turpis, dignissim vel, ornare ac, +ultricies quis, magna. Phasellus lacinia, augue ac dictum tempor, nisi felis +ornare magna, eu vehicula tellus enim eu neque. Fusce est eros, sagittis eget, +interdum a, ornare suscipit, massa. Sed vehicula elementum ligula. Aliquam erat +volutpat. Donec odio. Quisque nunc. Integer cursus feugiat magna. Fusce ac elit +ut elit aliquam suscipit. Duis leo est, interdum nec, varius in, facilisis +vitae, odio. Phasellus eget leo at urna adipiscing vulputate. Nam eu erat vel +arcu tristique mattis. Nullam placerat lorem non augue. Cras et velit. Morbi +sapien nulla, volutpat a, tristique eu, molestie ac, felis. + +Suspendisse sit amet tellus non odio porta pellentesque. Nulla facilisi. +Integer iaculis condimentum augue. Nullam urna nulla, vestibulum quis, lacinia +eget, ullamcorper eu, dui. Quisque dignissim consequat nisl. Pellentesque porta +augue in diam. Duis mattis. Aliquam et mi quis turpis pellentesque consequat. +Suspendisse nulla erat, lacinia nec, pretium vitae, feugiat ac, quam. Etiam sed +tellus vel est ultrices condimentum. Vestibulum euismod. Vivamus blandit. +Pellentesque eu urna. Vestibulum consequat sem vitae dui. In dictum feugiat +quam. Phasellus placerat. In sem nisl, elementum vitae, venenatis nec, lacinia +ac, arcu. Pellentesque gravida egestas mi. Integer rutrum tincidunt libero. + +Duis viverra. Nulla diam lectus, tincidunt et, scelerisque vitae, aliquam +vitae, justo. Quisque eget erat. Donec aliquet porta magna. Sed nisl. Ut +tellus. Suspendisse quis mi eget dolor sagittis tristique. Aenean non pede eget +nisl bibendum gravida. Class aptent taciti sociosqu ad litora torquent per +conubia nostra, per inceptos himenaeos. Morbi laoreet. Suspendisse potenti. +Donec accumsan porta felis. + +Fusce tristique leo quis pede. Cras nibh. Sed eget est vitae tortor mollis +ullamcorper. Suspendisse placerat dolor a dui. Vestibulum condimentum dui et +elit. Pellentesque porttitor ipsum at ipsum. Nam massa. Duis lorem. Donec +porta. Proin ligula. Aenean nunc massa, dapibus quis, imperdiet id, commodo a, +lacus. Cras sit amet. + diff --git a/csunplugged/tests/topics/loaders/assets/glossary_terms/glossary_folder/glossary-term-1.md b/csunplugged/tests/topics/loaders/assets/glossary_terms/glossary_folder/glossary-term-1.md new file mode 100644 index 000000000..c65f92dd2 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/glossary_terms/glossary_folder/glossary-term-1.md @@ -0,0 +1,3 @@ +# Glossary Term 1 + +Algorithms. They are great. \ No newline at end of file diff --git a/csunplugged/tests/topics/loaders/assets/learning_outcomes/basic-config.yaml b/csunplugged/tests/topics/loaders/assets/learning_outcomes/basic-config.yaml new file mode 100644 index 000000000..2f9cd479c --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/learning_outcomes/basic-config.yaml @@ -0,0 +1,2 @@ +no-physical-zeros-ones: + text: Justify why there aren’t actual 0’s and 1’s zooming around inside a computer. diff --git a/csunplugged/tests/topics/loaders/assets/lessons/basic-config.yaml b/csunplugged/tests/topics/loaders/assets/lessons/basic-config.yaml new file mode 100644 index 000000000..058150fe2 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/basic-config.yaml @@ -0,0 +1,2 @@ +lesson-1: + number: 1 diff --git a/csunplugged/tests/topics/loaders/assets/lessons/classroom-resources.yaml b/csunplugged/tests/topics/loaders/assets/lessons/classroom-resources.yaml new file mode 100644 index 000000000..212f9eaf6 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/classroom-resources.yaml @@ -0,0 +1,7 @@ +lesson-1: + min-age: 1 + max-age: 99 + number: 1 + classroom-resources: + - Pens + - Paper diff --git a/csunplugged/tests/topics/loaders/assets/lessons/ct-links-text.md b/csunplugged/tests/topics/loaders/assets/lessons/ct-links-text.md new file mode 100644 index 000000000..69ddf870c --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/ct-links-text.md @@ -0,0 +1 @@ +Example text for Computational Thinking links. diff --git a/csunplugged/tests/topics/loaders/assets/lessons/ct-links.md b/csunplugged/tests/topics/loaders/assets/lessons/ct-links.md new file mode 100644 index 000000000..c60990dcc --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/ct-links.md @@ -0,0 +1,3 @@ +# Lesson 1 + +Etiam in massa. Nam ut metus. In rhoncus venenatis tellus. diff --git a/csunplugged/tests/topics/loaders/assets/lessons/ct-links.yaml b/csunplugged/tests/topics/loaders/assets/lessons/ct-links.yaml new file mode 100644 index 000000000..980b862f3 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/ct-links.yaml @@ -0,0 +1,3 @@ +ct-links: + number: 1 + computational-thinking-links: ct-links-text.md diff --git a/csunplugged/tests/topics/loaders/assets/lessons/duration.yaml b/csunplugged/tests/topics/loaders/assets/lessons/duration.yaml new file mode 100644 index 000000000..1ebae8724 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/duration.yaml @@ -0,0 +1,3 @@ +lesson-1: + number: 1 + duration: 60 diff --git a/csunplugged/tests/topics/loaders/assets/lessons/empty.yaml b/csunplugged/tests/topics/loaders/assets/lessons/empty.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/csunplugged/tests/topics/loaders/assets/lessons/heading-tree.yaml b/csunplugged/tests/topics/loaders/assets/lessons/heading-tree.yaml new file mode 100644 index 000000000..6c3d5f478 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/heading-tree.yaml @@ -0,0 +1,2 @@ +lesson-2: + number: 1 diff --git a/csunplugged/tests/topics/loaders/assets/lessons/learning-outcomes.yaml b/csunplugged/tests/topics/loaders/assets/lessons/learning-outcomes.yaml new file mode 100644 index 000000000..f8e905280 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/learning-outcomes.yaml @@ -0,0 +1,5 @@ +lesson-1: + number: 1 + learning-outcomes: + - outcome-1 + - outcome-2 diff --git a/csunplugged/tests/topics/loaders/assets/lessons/lesson-1.md b/csunplugged/tests/topics/loaders/assets/lessons/lesson-1.md new file mode 100644 index 000000000..c60990dcc --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/lesson-1.md @@ -0,0 +1,3 @@ +# Lesson 1 + +Etiam in massa. Nam ut metus. In rhoncus venenatis tellus. diff --git a/csunplugged/tests/topics/loaders/assets/lessons/lesson-2.md b/csunplugged/tests/topics/loaders/assets/lessons/lesson-2.md new file mode 100644 index 000000000..e76d3dc25 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/lesson-2.md @@ -0,0 +1,48 @@ +# Lesson 2 + +Etiam in massa. Nam ut metus. In rhoncus venenatis tellus. Etiam aliquam. Ut +aliquam lectus ut lectus. Nam turpis lacus, tristique sit amet, convallis +sollicitudin, commodo a, purus. Nulla vitae eros a diam blandit mollis. Proin +luctus feugiat eros. Pellentesque habitant morbi tristique senectus et netus et +malesuada fames ac turpis egestas. Duis ultricies urna. Etiam enim urna, +pharetra suscipit, varius et, congue quis, odio. Donec lobortis, elit bibendum +euismod faucibus, velit nibh egestas libero, vitae pellentesque elit augue ut +massa. + +## Heading 2 + +Nulla consequat erat at massa. Vivamus id mi. Morbi purus enim, dapibus a, +facilisis non, tincidunt at, enim. Vestibulum ante ipsum primis in faucibus +orci luctus et ultrices posuere cubilia Curae; Duis imperdiet eleifend arcu. +Cras magna ligula, consequat at, tempor non, posuere nec, libero. Vestibulum +vel ipsum. Praesent congue justo et nunc. Vestibulum nec felis vitae nisl +pharetra sollicitudin. Quisque nec arcu vel tellus tristique vestibulum. Aenean +vel lacus. Mauris dolor erat, commodo ut, dapibus vehicula, lobortis sit amet, +orci. Aliquam augue. In semper nisi nec libero. Cras magna ipsum, scelerisque +et, tempor eget, gravida nec, lacus. Fusce eros nisi, ullamcorper blandit, +ultricies eget, elementum eget, pede. Phasellus id risus vitae nisl ullamcorper +congue. Proin est. + +### Heading 3 + +Sed eleifend odio sed leo. Mauris tortor turpis, dignissim vel, ornare ac, +ultricies quis, magna. Phasellus lacinia, augue ac dictum tempor, nisi felis +ornare magna, eu vehicula tellus enim eu neque. Fusce est eros, sagittis eget, +interdum a, ornare suscipit, massa. Sed vehicula elementum ligula. Aliquam erat +volutpat. Donec odio. Quisque nunc. Integer cursus feugiat magna. Fusce ac elit +ut elit aliquam suscipit. Duis leo est, interdum nec, varius in, facilisis +vitae, odio. Phasellus eget leo at urna adipiscing vulputate. Nam eu erat vel +arcu tristique mattis. Nullam placerat lorem non augue. Cras et velit. Morbi +sapien nulla, volutpat a, tristique eu, molestie ac, felis. + +## Heading 4 + +Suspendisse sit amet tellus non odio porta pellentesque. Nulla facilisi. +Integer iaculis condimentum augue. Nullam urna nulla, vestibulum quis, lacinia +eget, ullamcorper eu, dui. Quisque dignissim consequat nisl. Pellentesque porta +augue in diam. Duis mattis. Aliquam et mi quis turpis pellentesque consequat. +Suspendisse nulla erat, lacinia nec, pretium vitae, feugiat ac, quam. Etiam sed +tellus vel est ultrices condimentum. Vestibulum euismod. Vivamus blandit. +Pellentesque eu urna. Vestibulum consequat sem vitae dui. In dictum feugiat +quam. Phasellus placerat. In sem nisl, elementum vitae, venenatis nec, lacinia +ac, arcu. Pellentesque gravida egestas mi. Integer rutrum tincidunt libero. diff --git a/csunplugged/tests/topics/loaders/assets/lessons/lesson-3.md b/csunplugged/tests/topics/loaders/assets/lessons/lesson-3.md new file mode 100644 index 000000000..eaf62636b --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/lesson-3.md @@ -0,0 +1,3 @@ +# Lesson 3 + +Etiam in massa. Nam ut metus. In rhoncus venenatis tellus. diff --git a/csunplugged/tests/topics/loaders/assets/lessons/lesson-missing-content.md b/csunplugged/tests/topics/loaders/assets/lessons/lesson-missing-content.md new file mode 100644 index 000000000..da5c9797e --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/lesson-missing-content.md @@ -0,0 +1 @@ +# Lesson 3 diff --git a/csunplugged/tests/topics/loaders/assets/lessons/lesson-missing-title.md b/csunplugged/tests/topics/loaders/assets/lessons/lesson-missing-title.md new file mode 100644 index 000000000..b14f28677 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/lesson-missing-title.md @@ -0,0 +1 @@ +Etiam in massa. Nam ut metus. In rhoncus venenatis tellus. diff --git a/csunplugged/tests/topics/loaders/assets/lessons/missing-content.yaml b/csunplugged/tests/topics/loaders/assets/lessons/missing-content.yaml new file mode 100644 index 000000000..887754d9e --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/missing-content.yaml @@ -0,0 +1,2 @@ +lesson-missing-content: + number: 1 diff --git a/csunplugged/tests/topics/loaders/assets/lessons/missing-lesson-data.yaml b/csunplugged/tests/topics/loaders/assets/lessons/missing-lesson-data.yaml new file mode 100644 index 000000000..662c102b3 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/missing-lesson-data.yaml @@ -0,0 +1 @@ +lesson-1: diff --git a/csunplugged/tests/topics/loaders/assets/lessons/missing-number.yaml b/csunplugged/tests/topics/loaders/assets/lessons/missing-number.yaml new file mode 100644 index 000000000..662c102b3 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/missing-number.yaml @@ -0,0 +1 @@ +lesson-1: diff --git a/csunplugged/tests/topics/loaders/assets/lessons/missing-title.yaml b/csunplugged/tests/topics/loaders/assets/lessons/missing-title.yaml new file mode 100644 index 000000000..14a5351ba --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/missing-title.yaml @@ -0,0 +1,2 @@ +lesson-missing-title: + number: 1 diff --git a/csunplugged/tests/topics/loaders/assets/lessons/multiple-lessons.yaml b/csunplugged/tests/topics/loaders/assets/lessons/multiple-lessons.yaml new file mode 100644 index 000000000..c5e7814c5 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/multiple-lessons.yaml @@ -0,0 +1,8 @@ +lesson-1: + number: 1 + +lesson-2: + number: 2 + +lesson-3: + number: 3 diff --git a/csunplugged/tests/topics/loaders/assets/lessons/programming-challenges-description-text.md b/csunplugged/tests/topics/loaders/assets/lessons/programming-challenges-description-text.md new file mode 100644 index 000000000..9a31a3b25 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/programming-challenges-description-text.md @@ -0,0 +1 @@ +Description of lesson programming challenges. diff --git a/csunplugged/tests/topics/loaders/assets/lessons/programming-challenges-description.md b/csunplugged/tests/topics/loaders/assets/lessons/programming-challenges-description.md new file mode 100644 index 000000000..c60990dcc --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/programming-challenges-description.md @@ -0,0 +1,3 @@ +# Lesson 1 + +Etiam in massa. Nam ut metus. In rhoncus venenatis tellus. diff --git a/csunplugged/tests/topics/loaders/assets/lessons/programming-challenges-description.yaml b/csunplugged/tests/topics/loaders/assets/lessons/programming-challenges-description.yaml new file mode 100644 index 000000000..ad0cdfec9 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/programming-challenges-description.yaml @@ -0,0 +1,3 @@ +programming-challenges-description: + number: 1 + programming-challenges-description: programming-challenges-description-text.md diff --git a/csunplugged/tests/topics/loaders/assets/lessons/programming-challenges-invalid.yaml b/csunplugged/tests/topics/loaders/assets/lessons/programming-challenges-invalid.yaml new file mode 100644 index 000000000..aa133d70a --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/programming-challenges-invalid.yaml @@ -0,0 +1,4 @@ +lesson-1: + number: 1 + programming-challenges: + - challenge-1 diff --git a/csunplugged/tests/topics/loaders/assets/lessons/programming-challenges.yaml b/csunplugged/tests/topics/loaders/assets/lessons/programming-challenges.yaml new file mode 100644 index 000000000..1f10038f7 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/lessons/programming-challenges.yaml @@ -0,0 +1,5 @@ +lesson-1: + number: 1 + programming-challenges: + - challenge-1 + - challenge-2 diff --git a/csunplugged/tests/topics/loaders/assets/programming_challenges/basic-config.yaml b/csunplugged/tests/topics/loaders/assets/programming_challenges/basic-config.yaml new file mode 100644 index 000000000..6a2817e26 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/programming_challenges/basic-config.yaml @@ -0,0 +1,6 @@ +programming-challenge-1: + challenge-set-number: 1 + challenge-number: 1 + difficulty-level: 1 + programming-languages: + - language-1 diff --git a/csunplugged/tests/topics/loaders/assets/programming_challenges/programming-challenge-1/language-1-expected.md b/csunplugged/tests/topics/loaders/assets/programming_challenges/programming-challenge-1/language-1-expected.md new file mode 100644 index 000000000..f41b60600 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/programming_challenges/programming-challenge-1/language-1-expected.md @@ -0,0 +1,54 @@ +Etiam in massa. Nam ut metus. In rhoncus venenatis tellus. Etiam aliquam. Ut +aliquam lectus ut lectus. Nam turpis lacus, tristique sit amet, convallis +sollicitudin, commodo a, purus. Nulla vitae eros a diam blandit mollis. Proin +luctus feugiat eros. Pellentesque habitant morbi tristique senectus et netus et +malesuada fames ac turpis egestas. Duis ultricies urna. Etiam enim urna, +pharetra suscipit, varius et, congue quis, odio. Donec lobortis, elit bibendum +euismod faucibus, velit nibh egestas libero, vitae pellentesque elit augue ut +massa. + +Nulla consequat erat at massa. Vivamus id mi. Morbi purus enim, dapibus a, +facilisis non, tincidunt at, enim. Vestibulum ante ipsum primis in faucibus +orci luctus et ultrices posuere cubilia Curae; Duis imperdiet eleifend arcu. +Cras magna ligula, consequat at, tempor non, posuere nec, libero. Vestibulum +vel ipsum. Praesent congue justo et nunc. Vestibulum nec felis vitae nisl +pharetra sollicitudin. Quisque nec arcu vel tellus tristique vestibulum. Aenean +vel lacus. Mauris dolor erat, commodo ut, dapibus vehicula, lobortis sit amet, +orci. Aliquam augue. In semper nisi nec libero. Cras magna ipsum, scelerisque +et, tempor eget, gravida nec, lacus. Fusce eros nisi, ullamcorper blandit, +ultricies eget, elementum eget, pede. Phasellus id risus vitae nisl ullamcorper +congue. Proin est. + +Sed eleifend odio sed leo. Mauris tortor turpis, dignissim vel, ornare ac, +ultricies quis, magna. Phasellus lacinia, augue ac dictum tempor, nisi felis +ornare magna, eu vehicula tellus enim eu neque. Fusce est eros, sagittis eget, +interdum a, ornare suscipit, massa. Sed vehicula elementum ligula. Aliquam erat +volutpat. Donec odio. Quisque nunc. Integer cursus feugiat magna. Fusce ac elit +ut elit aliquam suscipit. Duis leo est, interdum nec, varius in, facilisis +vitae, odio. Phasellus eget leo at urna adipiscing vulputate. Nam eu erat vel +arcu tristique mattis. Nullam placerat lorem non augue. Cras et velit. Morbi +sapien nulla, volutpat a, tristique eu, molestie ac, felis. + +Suspendisse sit amet tellus non odio porta pellentesque. Nulla facilisi. +Integer iaculis condimentum augue. Nullam urna nulla, vestibulum quis, lacinia +eget, ullamcorper eu, dui. Quisque dignissim consequat nisl. Pellentesque porta +augue in diam. Duis mattis. Aliquam et mi quis turpis pellentesque consequat. +Suspendisse nulla erat, lacinia nec, pretium vitae, feugiat ac, quam. Etiam sed +tellus vel est ultrices condimentum. Vestibulum euismod. Vivamus blandit. +Pellentesque eu urna. Vestibulum consequat sem vitae dui. In dictum feugiat +quam. Phasellus placerat. In sem nisl, elementum vitae, venenatis nec, lacinia +ac, arcu. Pellentesque gravida egestas mi. Integer rutrum tincidunt libero. + +Duis viverra. Nulla diam lectus, tincidunt et, scelerisque vitae, aliquam +vitae, justo. Quisque eget erat. Donec aliquet porta magna. Sed nisl. Ut +tellus. Suspendisse quis mi eget dolor sagittis tristique. Aenean non pede eget +nisl bibendum gravida. Class aptent taciti sociosqu ad litora torquent per +conubia nostra, per inceptos himenaeos. Morbi laoreet. Suspendisse potenti. +Donec accumsan porta felis. + +Fusce tristique leo quis pede. Cras nibh. Sed eget est vitae tortor mollis +ullamcorper. Suspendisse placerat dolor a dui. Vestibulum condimentum dui et +elit. Pellentesque porttitor ipsum at ipsum. Nam massa. Duis lorem. Donec +porta. Proin ligula. Aenean nunc massa, dapibus quis, imperdiet id, commodo a, +lacus. Cras sit amet. + diff --git a/csunplugged/tests/topics/loaders/assets/programming_challenges/programming-challenge-1/language-1-hints.md b/csunplugged/tests/topics/loaders/assets/programming_challenges/programming-challenge-1/language-1-hints.md new file mode 100644 index 000000000..f41b60600 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/programming_challenges/programming-challenge-1/language-1-hints.md @@ -0,0 +1,54 @@ +Etiam in massa. Nam ut metus. In rhoncus venenatis tellus. Etiam aliquam. Ut +aliquam lectus ut lectus. Nam turpis lacus, tristique sit amet, convallis +sollicitudin, commodo a, purus. Nulla vitae eros a diam blandit mollis. Proin +luctus feugiat eros. Pellentesque habitant morbi tristique senectus et netus et +malesuada fames ac turpis egestas. Duis ultricies urna. Etiam enim urna, +pharetra suscipit, varius et, congue quis, odio. Donec lobortis, elit bibendum +euismod faucibus, velit nibh egestas libero, vitae pellentesque elit augue ut +massa. + +Nulla consequat erat at massa. Vivamus id mi. Morbi purus enim, dapibus a, +facilisis non, tincidunt at, enim. Vestibulum ante ipsum primis in faucibus +orci luctus et ultrices posuere cubilia Curae; Duis imperdiet eleifend arcu. +Cras magna ligula, consequat at, tempor non, posuere nec, libero. Vestibulum +vel ipsum. Praesent congue justo et nunc. Vestibulum nec felis vitae nisl +pharetra sollicitudin. Quisque nec arcu vel tellus tristique vestibulum. Aenean +vel lacus. Mauris dolor erat, commodo ut, dapibus vehicula, lobortis sit amet, +orci. Aliquam augue. In semper nisi nec libero. Cras magna ipsum, scelerisque +et, tempor eget, gravida nec, lacus. Fusce eros nisi, ullamcorper blandit, +ultricies eget, elementum eget, pede. Phasellus id risus vitae nisl ullamcorper +congue. Proin est. + +Sed eleifend odio sed leo. Mauris tortor turpis, dignissim vel, ornare ac, +ultricies quis, magna. Phasellus lacinia, augue ac dictum tempor, nisi felis +ornare magna, eu vehicula tellus enim eu neque. Fusce est eros, sagittis eget, +interdum a, ornare suscipit, massa. Sed vehicula elementum ligula. Aliquam erat +volutpat. Donec odio. Quisque nunc. Integer cursus feugiat magna. Fusce ac elit +ut elit aliquam suscipit. Duis leo est, interdum nec, varius in, facilisis +vitae, odio. Phasellus eget leo at urna adipiscing vulputate. Nam eu erat vel +arcu tristique mattis. Nullam placerat lorem non augue. Cras et velit. Morbi +sapien nulla, volutpat a, tristique eu, molestie ac, felis. + +Suspendisse sit amet tellus non odio porta pellentesque. Nulla facilisi. +Integer iaculis condimentum augue. Nullam urna nulla, vestibulum quis, lacinia +eget, ullamcorper eu, dui. Quisque dignissim consequat nisl. Pellentesque porta +augue in diam. Duis mattis. Aliquam et mi quis turpis pellentesque consequat. +Suspendisse nulla erat, lacinia nec, pretium vitae, feugiat ac, quam. Etiam sed +tellus vel est ultrices condimentum. Vestibulum euismod. Vivamus blandit. +Pellentesque eu urna. Vestibulum consequat sem vitae dui. In dictum feugiat +quam. Phasellus placerat. In sem nisl, elementum vitae, venenatis nec, lacinia +ac, arcu. Pellentesque gravida egestas mi. Integer rutrum tincidunt libero. + +Duis viverra. Nulla diam lectus, tincidunt et, scelerisque vitae, aliquam +vitae, justo. Quisque eget erat. Donec aliquet porta magna. Sed nisl. Ut +tellus. Suspendisse quis mi eget dolor sagittis tristique. Aenean non pede eget +nisl bibendum gravida. Class aptent taciti sociosqu ad litora torquent per +conubia nostra, per inceptos himenaeos. Morbi laoreet. Suspendisse potenti. +Donec accumsan porta felis. + +Fusce tristique leo quis pede. Cras nibh. Sed eget est vitae tortor mollis +ullamcorper. Suspendisse placerat dolor a dui. Vestibulum condimentum dui et +elit. Pellentesque porttitor ipsum at ipsum. Nam massa. Duis lorem. Donec +porta. Proin ligula. Aenean nunc massa, dapibus quis, imperdiet id, commodo a, +lacus. Cras sit amet. + diff --git a/csunplugged/tests/topics/loaders/assets/programming_challenges/programming-challenge-1/language-1-solution.md b/csunplugged/tests/topics/loaders/assets/programming_challenges/programming-challenge-1/language-1-solution.md new file mode 100644 index 000000000..f41b60600 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/programming_challenges/programming-challenge-1/language-1-solution.md @@ -0,0 +1,54 @@ +Etiam in massa. Nam ut metus. In rhoncus venenatis tellus. Etiam aliquam. Ut +aliquam lectus ut lectus. Nam turpis lacus, tristique sit amet, convallis +sollicitudin, commodo a, purus. Nulla vitae eros a diam blandit mollis. Proin +luctus feugiat eros. Pellentesque habitant morbi tristique senectus et netus et +malesuada fames ac turpis egestas. Duis ultricies urna. Etiam enim urna, +pharetra suscipit, varius et, congue quis, odio. Donec lobortis, elit bibendum +euismod faucibus, velit nibh egestas libero, vitae pellentesque elit augue ut +massa. + +Nulla consequat erat at massa. Vivamus id mi. Morbi purus enim, dapibus a, +facilisis non, tincidunt at, enim. Vestibulum ante ipsum primis in faucibus +orci luctus et ultrices posuere cubilia Curae; Duis imperdiet eleifend arcu. +Cras magna ligula, consequat at, tempor non, posuere nec, libero. Vestibulum +vel ipsum. Praesent congue justo et nunc. Vestibulum nec felis vitae nisl +pharetra sollicitudin. Quisque nec arcu vel tellus tristique vestibulum. Aenean +vel lacus. Mauris dolor erat, commodo ut, dapibus vehicula, lobortis sit amet, +orci. Aliquam augue. In semper nisi nec libero. Cras magna ipsum, scelerisque +et, tempor eget, gravida nec, lacus. Fusce eros nisi, ullamcorper blandit, +ultricies eget, elementum eget, pede. Phasellus id risus vitae nisl ullamcorper +congue. Proin est. + +Sed eleifend odio sed leo. Mauris tortor turpis, dignissim vel, ornare ac, +ultricies quis, magna. Phasellus lacinia, augue ac dictum tempor, nisi felis +ornare magna, eu vehicula tellus enim eu neque. Fusce est eros, sagittis eget, +interdum a, ornare suscipit, massa. Sed vehicula elementum ligula. Aliquam erat +volutpat. Donec odio. Quisque nunc. Integer cursus feugiat magna. Fusce ac elit +ut elit aliquam suscipit. Duis leo est, interdum nec, varius in, facilisis +vitae, odio. Phasellus eget leo at urna adipiscing vulputate. Nam eu erat vel +arcu tristique mattis. Nullam placerat lorem non augue. Cras et velit. Morbi +sapien nulla, volutpat a, tristique eu, molestie ac, felis. + +Suspendisse sit amet tellus non odio porta pellentesque. Nulla facilisi. +Integer iaculis condimentum augue. Nullam urna nulla, vestibulum quis, lacinia +eget, ullamcorper eu, dui. Quisque dignissim consequat nisl. Pellentesque porta +augue in diam. Duis mattis. Aliquam et mi quis turpis pellentesque consequat. +Suspendisse nulla erat, lacinia nec, pretium vitae, feugiat ac, quam. Etiam sed +tellus vel est ultrices condimentum. Vestibulum euismod. Vivamus blandit. +Pellentesque eu urna. Vestibulum consequat sem vitae dui. In dictum feugiat +quam. Phasellus placerat. In sem nisl, elementum vitae, venenatis nec, lacinia +ac, arcu. Pellentesque gravida egestas mi. Integer rutrum tincidunt libero. + +Duis viverra. Nulla diam lectus, tincidunt et, scelerisque vitae, aliquam +vitae, justo. Quisque eget erat. Donec aliquet porta magna. Sed nisl. Ut +tellus. Suspendisse quis mi eget dolor sagittis tristique. Aenean non pede eget +nisl bibendum gravida. Class aptent taciti sociosqu ad litora torquent per +conubia nostra, per inceptos himenaeos. Morbi laoreet. Suspendisse potenti. +Donec accumsan porta felis. + +Fusce tristique leo quis pede. Cras nibh. Sed eget est vitae tortor mollis +ullamcorper. Suspendisse placerat dolor a dui. Vestibulum condimentum dui et +elit. Pellentesque porttitor ipsum at ipsum. Nam massa. Duis lorem. Donec +porta. Proin ligula. Aenean nunc massa, dapibus quis, imperdiet id, commodo a, +lacus. Cras sit amet. + diff --git a/csunplugged/tests/topics/loaders/assets/programming_challenges/programming-challenge-1/programming-challenge-1.md b/csunplugged/tests/topics/loaders/assets/programming_challenges/programming-challenge-1/programming-challenge-1.md new file mode 100644 index 000000000..7783ee302 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/programming_challenges/programming-challenge-1/programming-challenge-1.md @@ -0,0 +1,56 @@ +# Programming Challenge 1 + +Etiam in massa. Nam ut metus. In rhoncus venenatis tellus. Etiam aliquam. Ut +aliquam lectus ut lectus. Nam turpis lacus, tristique sit amet, convallis +sollicitudin, commodo a, purus. Nulla vitae eros a diam blandit mollis. Proin +luctus feugiat eros. Pellentesque habitant morbi tristique senectus et netus et +malesuada fames ac turpis egestas. Duis ultricies urna. Etiam enim urna, +pharetra suscipit, varius et, congue quis, odio. Donec lobortis, elit bibendum +euismod faucibus, velit nibh egestas libero, vitae pellentesque elit augue ut +massa. + +Nulla consequat erat at massa. Vivamus id mi. Morbi purus enim, dapibus a, +facilisis non, tincidunt at, enim. Vestibulum ante ipsum primis in faucibus +orci luctus et ultrices posuere cubilia Curae; Duis imperdiet eleifend arcu. +Cras magna ligula, consequat at, tempor non, posuere nec, libero. Vestibulum +vel ipsum. Praesent congue justo et nunc. Vestibulum nec felis vitae nisl +pharetra sollicitudin. Quisque nec arcu vel tellus tristique vestibulum. Aenean +vel lacus. Mauris dolor erat, commodo ut, dapibus vehicula, lobortis sit amet, +orci. Aliquam augue. In semper nisi nec libero. Cras magna ipsum, scelerisque +et, tempor eget, gravida nec, lacus. Fusce eros nisi, ullamcorper blandit, +ultricies eget, elementum eget, pede. Phasellus id risus vitae nisl ullamcorper +congue. Proin est. + +Sed eleifend odio sed leo. Mauris tortor turpis, dignissim vel, ornare ac, +ultricies quis, magna. Phasellus lacinia, augue ac dictum tempor, nisi felis +ornare magna, eu vehicula tellus enim eu neque. Fusce est eros, sagittis eget, +interdum a, ornare suscipit, massa. Sed vehicula elementum ligula. Aliquam erat +volutpat. Donec odio. Quisque nunc. Integer cursus feugiat magna. Fusce ac elit +ut elit aliquam suscipit. Duis leo est, interdum nec, varius in, facilisis +vitae, odio. Phasellus eget leo at urna adipiscing vulputate. Nam eu erat vel +arcu tristique mattis. Nullam placerat lorem non augue. Cras et velit. Morbi +sapien nulla, volutpat a, tristique eu, molestie ac, felis. + +Suspendisse sit amet tellus non odio porta pellentesque. Nulla facilisi. +Integer iaculis condimentum augue. Nullam urna nulla, vestibulum quis, lacinia +eget, ullamcorper eu, dui. Quisque dignissim consequat nisl. Pellentesque porta +augue in diam. Duis mattis. Aliquam et mi quis turpis pellentesque consequat. +Suspendisse nulla erat, lacinia nec, pretium vitae, feugiat ac, quam. Etiam sed +tellus vel est ultrices condimentum. Vestibulum euismod. Vivamus blandit. +Pellentesque eu urna. Vestibulum consequat sem vitae dui. In dictum feugiat +quam. Phasellus placerat. In sem nisl, elementum vitae, venenatis nec, lacinia +ac, arcu. Pellentesque gravida egestas mi. Integer rutrum tincidunt libero. + +Duis viverra. Nulla diam lectus, tincidunt et, scelerisque vitae, aliquam +vitae, justo. Quisque eget erat. Donec aliquet porta magna. Sed nisl. Ut +tellus. Suspendisse quis mi eget dolor sagittis tristique. Aenean non pede eget +nisl bibendum gravida. Class aptent taciti sociosqu ad litora torquent per +conubia nostra, per inceptos himenaeos. Morbi laoreet. Suspendisse potenti. +Donec accumsan porta felis. + +Fusce tristique leo quis pede. Cras nibh. Sed eget est vitae tortor mollis +ullamcorper. Suspendisse placerat dolor a dui. Vestibulum condimentum dui et +elit. Pellentesque porttitor ipsum at ipsum. Nam massa. Duis lorem. Donec +porta. Proin ligula. Aenean nunc massa, dapibus quis, imperdiet id, commodo a, +lacus. Cras sit amet. + diff --git a/csunplugged/tests/topics/loaders/assets/programming_challenges_structure/basic-config.yaml b/csunplugged/tests/topics/loaders/assets/programming_challenges_structure/basic-config.yaml new file mode 100644 index 000000000..8456b548c --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/programming_challenges_structure/basic-config.yaml @@ -0,0 +1,8 @@ +languages: + language-1: + name: Language 1 + number: 1 + +difficulties: + 1: + name: Level 1 diff --git a/csunplugged/tests/topics/loaders/assets/topic/topic-1/topic-1.md b/csunplugged/tests/topics/loaders/assets/topic/topic-1/topic-1.md new file mode 100644 index 000000000..f7db3e2eb --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/topic/topic-1/topic-1.md @@ -0,0 +1,3 @@ +# Topic 1 + +Etiam in massa. Nam ut metus. In rhoncus venenatis tellus. diff --git a/csunplugged/tests/topics/loaders/assets/topic/topic-1/topic-1.yaml b/csunplugged/tests/topics/loaders/assets/topic/topic-1/topic-1.yaml new file mode 100644 index 000000000..f21959299 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/topic/topic-1/topic-1.yaml @@ -0,0 +1,2 @@ +unit-plans: + - unit-plan-1 diff --git a/csunplugged/tests/topics/loaders/assets/topic/topic-missing-content/topic-missing-content.md b/csunplugged/tests/topics/loaders/assets/topic/topic-missing-content/topic-missing-content.md new file mode 100644 index 000000000..85fdee826 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/topic/topic-missing-content/topic-missing-content.md @@ -0,0 +1 @@ +# Topic 1 diff --git a/csunplugged/tests/topics/loaders/assets/topic/topic-missing-content/topic-missing-content.yaml b/csunplugged/tests/topics/loaders/assets/topic/topic-missing-content/topic-missing-content.yaml new file mode 100644 index 000000000..f21959299 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/topic/topic-missing-content/topic-missing-content.yaml @@ -0,0 +1,2 @@ +unit-plans: + - unit-plan-1 diff --git a/csunplugged/tests/topics/loaders/assets/topic/topic-missing-name/topic-missing-name.md b/csunplugged/tests/topics/loaders/assets/topic/topic-missing-name/topic-missing-name.md new file mode 100644 index 000000000..b14f28677 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/topic/topic-missing-name/topic-missing-name.md @@ -0,0 +1 @@ +Etiam in massa. Nam ut metus. In rhoncus venenatis tellus. diff --git a/csunplugged/tests/topics/loaders/assets/topic/topic-missing-name/topic-missing-name.yaml b/csunplugged/tests/topics/loaders/assets/topic/topic-missing-name/topic-missing-name.yaml new file mode 100644 index 000000000..f21959299 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/topic/topic-missing-name/topic-missing-name.yaml @@ -0,0 +1,2 @@ +unit-plans: + - unit-plan-1 diff --git a/csunplugged/tests/topics/loaders/assets/topic/topic-missing-unit-plans/other.md b/csunplugged/tests/topics/loaders/assets/topic/topic-missing-unit-plans/other.md new file mode 100644 index 000000000..b7c04a0cd --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/topic/topic-missing-unit-plans/other.md @@ -0,0 +1 @@ +# Other resources diff --git a/csunplugged/tests/topics/loaders/assets/topic/topic-missing-unit-plans/topic-missing-unit-plans.md b/csunplugged/tests/topics/loaders/assets/topic/topic-missing-unit-plans/topic-missing-unit-plans.md new file mode 100644 index 000000000..85fdee826 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/topic/topic-missing-unit-plans/topic-missing-unit-plans.md @@ -0,0 +1 @@ +# Topic 1 diff --git a/csunplugged/tests/topics/loaders/assets/topic/topic-missing-unit-plans/topic-missing-unit-plans.yaml b/csunplugged/tests/topics/loaders/assets/topic/topic-missing-unit-plans/topic-missing-unit-plans.yaml new file mode 100644 index 000000000..bc82cbdbf --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/topic/topic-missing-unit-plans/topic-missing-unit-plans.yaml @@ -0,0 +1 @@ +other-resources: other.md diff --git a/csunplugged/tests/topics/loaders/assets/topic/topic-valid-icon/topic-valid-icon.md b/csunplugged/tests/topics/loaders/assets/topic/topic-valid-icon/topic-valid-icon.md new file mode 100644 index 000000000..ce6a11bda --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/topic/topic-valid-icon/topic-valid-icon.md @@ -0,0 +1,3 @@ +# Topic Valid Icon + +Etiam in massa. Nam ut metus. In rhoncus venenatis tellus. diff --git a/csunplugged/tests/topics/loaders/assets/topic/topic-valid-icon/topic-valid-icon.yaml b/csunplugged/tests/topics/loaders/assets/topic/topic-valid-icon/topic-valid-icon.yaml new file mode 100644 index 000000000..dd7c5f3c6 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/topic/topic-valid-icon/topic-valid-icon.yaml @@ -0,0 +1,3 @@ +unit-plans: + - unit-plan-1 +icon: img/logo.png diff --git a/csunplugged/tests/topics/loaders/assets/unit_plan/ct-links/ct-links-text.md b/csunplugged/tests/topics/loaders/assets/unit_plan/ct-links/ct-links-text.md new file mode 100644 index 000000000..8afbdbae2 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/unit_plan/ct-links/ct-links-text.md @@ -0,0 +1 @@ +CT link text diff --git a/csunplugged/tests/topics/loaders/assets/unit_plan/ct-links/ct-links.md b/csunplugged/tests/topics/loaders/assets/unit_plan/ct-links/ct-links.md new file mode 100644 index 000000000..8f5aada04 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/unit_plan/ct-links/ct-links.md @@ -0,0 +1,3 @@ +# CT Links + +Etiam in massa. Nam ut metus. diff --git a/csunplugged/tests/topics/loaders/assets/unit_plan/ct-links/ct-links.yaml b/csunplugged/tests/topics/loaders/assets/unit_plan/ct-links/ct-links.yaml new file mode 100644 index 000000000..822e29fc9 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/unit_plan/ct-links/ct-links.yaml @@ -0,0 +1,7 @@ +lessons: lessons/lessons.yaml +computational-thinking-links: ct-links-text.md + +age-groups: + 8-10: + lesson-1: + number: 1 diff --git a/csunplugged/tests/topics/loaders/assets/unit_plan/missing-content/missing-content.md b/csunplugged/tests/topics/loaders/assets/unit_plan/missing-content/missing-content.md new file mode 100644 index 000000000..ca34edc2c --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/unit_plan/missing-content/missing-content.md @@ -0,0 +1 @@ +# Unit Plan 1 \ No newline at end of file diff --git a/csunplugged/tests/topics/loaders/assets/unit_plan/missing-content/missing-content.yaml b/csunplugged/tests/topics/loaders/assets/unit_plan/missing-content/missing-content.yaml new file mode 100644 index 000000000..564120bd7 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/unit_plan/missing-content/missing-content.yaml @@ -0,0 +1,6 @@ +lessons: lessons/lessons.yaml + +age-groups: + 8-10: + lesson-1: + number: 1 diff --git a/csunplugged/tests/topics/loaders/assets/unit_plan/missing-name/missing-name.md b/csunplugged/tests/topics/loaders/assets/unit_plan/missing-name/missing-name.md new file mode 100644 index 000000000..f41b60600 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/unit_plan/missing-name/missing-name.md @@ -0,0 +1,54 @@ +Etiam in massa. Nam ut metus. In rhoncus venenatis tellus. Etiam aliquam. Ut +aliquam lectus ut lectus. Nam turpis lacus, tristique sit amet, convallis +sollicitudin, commodo a, purus. Nulla vitae eros a diam blandit mollis. Proin +luctus feugiat eros. Pellentesque habitant morbi tristique senectus et netus et +malesuada fames ac turpis egestas. Duis ultricies urna. Etiam enim urna, +pharetra suscipit, varius et, congue quis, odio. Donec lobortis, elit bibendum +euismod faucibus, velit nibh egestas libero, vitae pellentesque elit augue ut +massa. + +Nulla consequat erat at massa. Vivamus id mi. Morbi purus enim, dapibus a, +facilisis non, tincidunt at, enim. Vestibulum ante ipsum primis in faucibus +orci luctus et ultrices posuere cubilia Curae; Duis imperdiet eleifend arcu. +Cras magna ligula, consequat at, tempor non, posuere nec, libero. Vestibulum +vel ipsum. Praesent congue justo et nunc. Vestibulum nec felis vitae nisl +pharetra sollicitudin. Quisque nec arcu vel tellus tristique vestibulum. Aenean +vel lacus. Mauris dolor erat, commodo ut, dapibus vehicula, lobortis sit amet, +orci. Aliquam augue. In semper nisi nec libero. Cras magna ipsum, scelerisque +et, tempor eget, gravida nec, lacus. Fusce eros nisi, ullamcorper blandit, +ultricies eget, elementum eget, pede. Phasellus id risus vitae nisl ullamcorper +congue. Proin est. + +Sed eleifend odio sed leo. Mauris tortor turpis, dignissim vel, ornare ac, +ultricies quis, magna. Phasellus lacinia, augue ac dictum tempor, nisi felis +ornare magna, eu vehicula tellus enim eu neque. Fusce est eros, sagittis eget, +interdum a, ornare suscipit, massa. Sed vehicula elementum ligula. Aliquam erat +volutpat. Donec odio. Quisque nunc. Integer cursus feugiat magna. Fusce ac elit +ut elit aliquam suscipit. Duis leo est, interdum nec, varius in, facilisis +vitae, odio. Phasellus eget leo at urna adipiscing vulputate. Nam eu erat vel +arcu tristique mattis. Nullam placerat lorem non augue. Cras et velit. Morbi +sapien nulla, volutpat a, tristique eu, molestie ac, felis. + +Suspendisse sit amet tellus non odio porta pellentesque. Nulla facilisi. +Integer iaculis condimentum augue. Nullam urna nulla, vestibulum quis, lacinia +eget, ullamcorper eu, dui. Quisque dignissim consequat nisl. Pellentesque porta +augue in diam. Duis mattis. Aliquam et mi quis turpis pellentesque consequat. +Suspendisse nulla erat, lacinia nec, pretium vitae, feugiat ac, quam. Etiam sed +tellus vel est ultrices condimentum. Vestibulum euismod. Vivamus blandit. +Pellentesque eu urna. Vestibulum consequat sem vitae dui. In dictum feugiat +quam. Phasellus placerat. In sem nisl, elementum vitae, venenatis nec, lacinia +ac, arcu. Pellentesque gravida egestas mi. Integer rutrum tincidunt libero. + +Duis viverra. Nulla diam lectus, tincidunt et, scelerisque vitae, aliquam +vitae, justo. Quisque eget erat. Donec aliquet porta magna. Sed nisl. Ut +tellus. Suspendisse quis mi eget dolor sagittis tristique. Aenean non pede eget +nisl bibendum gravida. Class aptent taciti sociosqu ad litora torquent per +conubia nostra, per inceptos himenaeos. Morbi laoreet. Suspendisse potenti. +Donec accumsan porta felis. + +Fusce tristique leo quis pede. Cras nibh. Sed eget est vitae tortor mollis +ullamcorper. Suspendisse placerat dolor a dui. Vestibulum condimentum dui et +elit. Pellentesque porttitor ipsum at ipsum. Nam massa. Duis lorem. Donec +porta. Proin ligula. Aenean nunc massa, dapibus quis, imperdiet id, commodo a, +lacus. Cras sit amet. + diff --git a/csunplugged/tests/topics/loaders/assets/unit_plan/missing-name/missing-name.yaml b/csunplugged/tests/topics/loaders/assets/unit_plan/missing-name/missing-name.yaml new file mode 100644 index 000000000..564120bd7 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/unit_plan/missing-name/missing-name.yaml @@ -0,0 +1,6 @@ +lessons: lessons/lessons.yaml + +age-groups: + 8-10: + lesson-1: + number: 1 diff --git a/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/missing-age-groups.yaml b/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/missing-age-groups.yaml new file mode 100644 index 000000000..054942893 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/missing-age-groups.yaml @@ -0,0 +1 @@ +lessons: lessons/lessons.yaml diff --git a/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/missing-lesson-keys.yaml b/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/missing-lesson-keys.yaml new file mode 100644 index 000000000..9fd317fc2 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/missing-lesson-keys.yaml @@ -0,0 +1,4 @@ +lessons: lessons/lessons.yaml + +age-groups: + 8-10: diff --git a/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/missing-lesson-number.yaml b/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/missing-lesson-number.yaml new file mode 100644 index 000000000..1125e39e7 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/missing-lesson-number.yaml @@ -0,0 +1,5 @@ +lessons: lessons/lessons.yaml + +age-groups: + 8-10: + lesson-1: diff --git a/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/missing-lessons-config.yaml b/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/missing-lessons-config.yaml new file mode 100644 index 000000000..a02c9f89b --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/missing-lessons-config.yaml @@ -0,0 +1,4 @@ +age-groups: + 8-10: + lesson-1: + number: 1 diff --git a/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/unit-plan-1.md b/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/unit-plan-1.md new file mode 100644 index 000000000..ddc378bba --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/unit-plan-1.md @@ -0,0 +1,56 @@ +# Unit Plan 1 + +Etiam in massa. Nam ut metus. In rhoncus venenatis tellus. Etiam aliquam. Ut +aliquam lectus ut lectus. Nam turpis lacus, tristique sit amet, convallis +sollicitudin, commodo a, purus. Nulla vitae eros a diam blandit mollis. Proin +luctus feugiat eros. Pellentesque habitant morbi tristique senectus et netus et +malesuada fames ac turpis egestas. Duis ultricies urna. Etiam enim urna, +pharetra suscipit, varius et, congue quis, odio. Donec lobortis, elit bibendum +euismod faucibus, velit nibh egestas libero, vitae pellentesque elit augue ut +massa. + +Nulla consequat erat at massa. Vivamus id mi. Morbi purus enim, dapibus a, +facilisis non, tincidunt at, enim. Vestibulum ante ipsum primis in faucibus +orci luctus et ultrices posuere cubilia Curae; Duis imperdiet eleifend arcu. +Cras magna ligula, consequat at, tempor non, posuere nec, libero. Vestibulum +vel ipsum. Praesent congue justo et nunc. Vestibulum nec felis vitae nisl +pharetra sollicitudin. Quisque nec arcu vel tellus tristique vestibulum. Aenean +vel lacus. Mauris dolor erat, commodo ut, dapibus vehicula, lobortis sit amet, +orci. Aliquam augue. In semper nisi nec libero. Cras magna ipsum, scelerisque +et, tempor eget, gravida nec, lacus. Fusce eros nisi, ullamcorper blandit, +ultricies eget, elementum eget, pede. Phasellus id risus vitae nisl ullamcorper +congue. Proin est. + +Sed eleifend odio sed leo. Mauris tortor turpis, dignissim vel, ornare ac, +ultricies quis, magna. Phasellus lacinia, augue ac dictum tempor, nisi felis +ornare magna, eu vehicula tellus enim eu neque. Fusce est eros, sagittis eget, +interdum a, ornare suscipit, massa. Sed vehicula elementum ligula. Aliquam erat +volutpat. Donec odio. Quisque nunc. Integer cursus feugiat magna. Fusce ac elit +ut elit aliquam suscipit. Duis leo est, interdum nec, varius in, facilisis +vitae, odio. Phasellus eget leo at urna adipiscing vulputate. Nam eu erat vel +arcu tristique mattis. Nullam placerat lorem non augue. Cras et velit. Morbi +sapien nulla, volutpat a, tristique eu, molestie ac, felis. + +Suspendisse sit amet tellus non odio porta pellentesque. Nulla facilisi. +Integer iaculis condimentum augue. Nullam urna nulla, vestibulum quis, lacinia +eget, ullamcorper eu, dui. Quisque dignissim consequat nisl. Pellentesque porta +augue in diam. Duis mattis. Aliquam et mi quis turpis pellentesque consequat. +Suspendisse nulla erat, lacinia nec, pretium vitae, feugiat ac, quam. Etiam sed +tellus vel est ultrices condimentum. Vestibulum euismod. Vivamus blandit. +Pellentesque eu urna. Vestibulum consequat sem vitae dui. In dictum feugiat +quam. Phasellus placerat. In sem nisl, elementum vitae, venenatis nec, lacinia +ac, arcu. Pellentesque gravida egestas mi. Integer rutrum tincidunt libero. + +Duis viverra. Nulla diam lectus, tincidunt et, scelerisque vitae, aliquam +vitae, justo. Quisque eget erat. Donec aliquet porta magna. Sed nisl. Ut +tellus. Suspendisse quis mi eget dolor sagittis tristique. Aenean non pede eget +nisl bibendum gravida. Class aptent taciti sociosqu ad litora torquent per +conubia nostra, per inceptos himenaeos. Morbi laoreet. Suspendisse potenti. +Donec accumsan porta felis. + +Fusce tristique leo quis pede. Cras nibh. Sed eget est vitae tortor mollis +ullamcorper. Suspendisse placerat dolor a dui. Vestibulum condimentum dui et +elit. Pellentesque porttitor ipsum at ipsum. Nam massa. Duis lorem. Donec +porta. Proin ligula. Aenean nunc massa, dapibus quis, imperdiet id, commodo a, +lacus. Cras sit amet. + diff --git a/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/unit-plan-1.yaml b/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/unit-plan-1.yaml new file mode 100644 index 000000000..564120bd7 --- /dev/null +++ b/csunplugged/tests/topics/loaders/assets/unit_plan/unit-plan-1/unit-plan-1.yaml @@ -0,0 +1,6 @@ +lessons: lessons/lessons.yaml + +age-groups: + 8-10: + lesson-1: + number: 1 diff --git a/csunplugged/tests/topics/loaders/test_age_groups_loader.py b/csunplugged/tests/topics/loaders/test_age_groups_loader.py new file mode 100644 index 000000000..33d0fbfc1 --- /dev/null +++ b/csunplugged/tests/topics/loaders/test_age_groups_loader.py @@ -0,0 +1,116 @@ +import os.path + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + +from topics.models import AgeGroup +from topics.management.commands._AgeGroupsLoader import AgeGroupsLoader + +from utils.errors.CouldNotFindConfigFileError import CouldNotFindConfigFileError +from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError +from utils.errors.EmptyConfigFileError import EmptyConfigFileError + + +class AgeGroupsLoaderTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = TopicsTestDataGenerator() + self.loader_name = "age_groups" + self.BASE_PATH = os.path.join(self.test_data.LOADER_ASSET_PATH, self.loader_name) + + def test_age_groups_loader_basic_config(self): + config_file = "basic-config.yaml" + group_loader = AgeGroupsLoader(config_file, self.BASE_PATH) + group_loader.load() + self.assertQuerysetEqual( + AgeGroup.objects.all(), + [""] + ) + + def test_age_groups_loader_missing_configuration_file(self): + config_file = "missing.yaml" + group_loader = AgeGroupsLoader(config_file, self.BASE_PATH) + self.assertRaises( + CouldNotFindConfigFileError, + group_loader.load, + ) + + def test_age_groups_loader_empty_configuration_file(self): + config_file = "empty.yaml" + group_loader = AgeGroupsLoader(config_file, self.BASE_PATH) + self.assertRaises( + EmptyConfigFileError, + group_loader.load, + ) + + def test_age_groups_loader_correct_slug_value(self): + config_file = "basic-config.yaml" + group_loader = AgeGroupsLoader(config_file, self.BASE_PATH) + group_loader.load() + self.assertEquals( + AgeGroup.objects.get(slug="8-10").slug, + "8-10", + ) + + def test_age_groups_loader_correct_min_age_value(self): + config_file = "basic-config.yaml" + group_loader = AgeGroupsLoader(config_file, self.BASE_PATH) + group_loader.load() + self.assertEquals( + AgeGroup.objects.get(slug="8-10").ages.lower, + 8, + ) + + def test_age_groups_loader_missing_min_age_value(self): + config_file = "missing-min-age.yaml" + group_loader = AgeGroupsLoader(config_file, self.BASE_PATH) + self.assertRaises( + MissingRequiredFieldError, + group_loader.load, + ) + + def test_age_groups_loader_correct_max_age_value(self): + config_file = "basic-config.yaml" + group_loader = AgeGroupsLoader(config_file, self.BASE_PATH) + group_loader.load() + self.assertEquals( + AgeGroup.objects.get(slug="8-10").ages.upper, + 10, + ) + + def test_age_groups_loader_missing_max_age_value(self): + config_file = "missing-max-age.yaml" + group_loader = AgeGroupsLoader(config_file, self.BASE_PATH) + self.assertRaises( + MissingRequiredFieldError, + group_loader.load, + ) + + def test_age_groups_loader_correct_description_value(self): + config_file = "description.yaml" + group_loader = AgeGroupsLoader(config_file, self.BASE_PATH) + group_loader.load() + self.assertEquals( + AgeGroup.objects.get(slug="5-7").description, + "Sample description.", + ) + + def test_age_groups_loader_missing_description_value(self): + config_file = "basic-config.yaml" + group_loader = AgeGroupsLoader(config_file, self.BASE_PATH) + group_loader.load() + self.assertIsNone(AgeGroup.objects.get(slug="8-10").description) + + def test_age_groups_loader_multiple_configuration(self): + config_file = "multiple.yaml" + group_loader = AgeGroupsLoader(config_file, self.BASE_PATH) + group_loader.load() + self.assertQuerysetEqual( + AgeGroup.objects.order_by("ages"), + [ + "", + "", + "", + ] + ) diff --git a/csunplugged/tests/topics/loaders/test_curriculum_areas_loader.py b/csunplugged/tests/topics/loaders/test_curriculum_areas_loader.py new file mode 100644 index 000000000..49ee31aab --- /dev/null +++ b/csunplugged/tests/topics/loaders/test_curriculum_areas_loader.py @@ -0,0 +1,205 @@ +import os.path + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + +from topics.models import CurriculumArea +from topics.management.commands._CurriculumAreasLoader import CurriculumAreasLoader + +from utils.errors.CouldNotFindConfigFileError import CouldNotFindConfigFileError +from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError +from utils.errors.EmptyConfigFileError import EmptyConfigFileError + + +class CurriculumAreasLoaderTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = TopicsTestDataGenerator() + self.loader_name = "curriculum_areas" + self.BASE_PATH = os.path.join(self.test_data.LOADER_ASSET_PATH, self.loader_name) + + def test_basic_curriculum_areas_loader_config(self): + config_file = "basic-config.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + area_loader.load() + self.assertQuerysetEqual( + CurriculumArea.objects.all(), + [""] + ) + + def test_curriculum_areas_loader_missing_configuration_file(self): + config_file = "missing.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + self.assertRaises( + CouldNotFindConfigFileError, + area_loader.load, + ) + + def test_curriculum_areas_loader_empty_configuration_file(self): + config_file = "empty.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + self.assertRaises( + EmptyConfigFileError, + area_loader.load, + ) + + def test_curriculum_areas_loader_correct_slug_value(self): + config_file = "basic-config.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + area_loader.load() + self.assertEquals( + CurriculumArea.objects.get(slug="maths").slug, + "maths", + ) + + def test_curriculum_areas_loader_correct_name_value(self): + config_file = "basic-config.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + area_loader.load() + self.assertEquals( + CurriculumArea.objects.get(slug="maths").name, + "Maths", + ) + + def test_curriculum_areas_loader_missing_name_value(self): + config_file = "missing-name.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + self.assertRaises( + MissingRequiredFieldError, + area_loader.load, + ) + + def test_curriculum_areas_loader_correct_number_value(self): + config_file = "basic-config.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + area_loader.load() + self.assertEquals( + CurriculumArea.objects.get(slug="maths").number, + 1, + ) + + def test_curriculum_areas_loader_missing_number_value(self): + config_file = "missing-number.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + self.assertRaises( + MissingRequiredFieldError, + area_loader.load, + ) + + def test_curriculum_areas_loader_correct_colour_value(self): + config_file = "basic-config.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + area_loader.load() + self.assertEquals( + CurriculumArea.objects.get(slug="maths").colour, + "blue", + ) + + def test_curriculum_areas_loader_missing_colour_value(self): + config_file = "missing-colour.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + self.assertRaises( + MissingRequiredFieldError, + area_loader.load, + ) + + def test_curriculum_areas_loader_correct_children_value(self): + config_file = "children.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + area_loader.load() + self.assertQuerysetEqual( + CurriculumArea.objects.all(), + [ + "", + "", + "", + ], + ordered=False, + ) + parent = CurriculumArea.objects.get(slug="maths") + self.assertQuerysetEqual( + CurriculumArea.objects.filter(parent=parent), + [ + "", + "", + ], + ordered=False, + ) + + def test_curriculum_areas_loader_missing_children_value(self): + config_file = "basic-config.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + area_loader.load() + self.assertQuerysetEqual( + CurriculumArea.objects.all(), + [""], + ) + + def test_curriculum_areas_loader_empty_children_value(self): + config_file = "empty-children.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + self.assertRaises( + MissingRequiredFieldError, + area_loader.load, + ) + + def test_curriculum_areas_loader_correct_children_slug_value(self): + config_file = "children.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + area_loader.load() + self.assertEqual( + CurriculumArea.objects.get(slug="algebra").slug, + "algebra", + ) + + def test_curriculum_areas_loader_correct_children_name_value(self): + config_file = "children.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + area_loader.load() + self.assertEquals( + CurriculumArea.objects.get(slug="algebra").name, + "Algebra", + ) + + def test_curriculum_areas_loader_missing_children_name_value(self): + config_file = "missing-children-name.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + self.assertRaises( + MissingRequiredFieldError, + area_loader.load, + ) + + def test_curriculum_areas_loader_correct_children_number_value(self): + config_file = "children.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + area_loader.load() + self.assertEquals( + CurriculumArea.objects.get(slug="algebra").number, + 1, + ) + + def test_curriculum_areas_loader_correct_children_colour_value(self): + config_file = "children.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + area_loader.load() + self.assertEquals( + CurriculumArea.objects.get(slug="algebra").colour, + "blue", + ) + + def test_curriculum_areas_loader_multiple_configuration(self): + config_file = "multiple.yaml" + area_loader = CurriculumAreasLoader(config_file, self.BASE_PATH) + area_loader.load() + self.assertQuerysetEqual( + CurriculumArea.objects.all(), + [ + "", + "", + "", + "", + "", + ], + ordered=False, + ) diff --git a/csunplugged/tests/topics/loaders/test_curriculum_integrations_loader.py b/csunplugged/tests/topics/loaders/test_curriculum_integrations_loader.py new file mode 100644 index 000000000..f6578d0b3 --- /dev/null +++ b/csunplugged/tests/topics/loaders/test_curriculum_integrations_loader.py @@ -0,0 +1,31 @@ +import os.path + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + +from topics.models import CurriculumIntegration +from topics.management.commands._CurriculumIntegrationsLoader import CurriculumIntegrationsLoader + + +class CurriculumIntegrationsLoaderTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = TopicsTestDataGenerator() + self.loader_name = "curriculum_integrations" + + def test_basic_config(self): + config_file = os.path.join(self.loader_name, "basic-config.yaml") + + self.test_data.create_curriculum_area("1") + topic = self.test_data.create_topic("1") + + ci_loader = CurriculumIntegrationsLoader(config_file, topic, self.test_data.LOADER_ASSET_PATH) + ci_loader.load() + + ci_objects = CurriculumIntegration.objects.all() + + self.assertQuerysetEqual( + ci_objects, + [""] + ) diff --git a/csunplugged/tests/topics/loaders/test_glossary_terms_loader.py b/csunplugged/tests/topics/loaders/test_glossary_terms_loader.py new file mode 100644 index 000000000..6e4f4654c --- /dev/null +++ b/csunplugged/tests/topics/loaders/test_glossary_terms_loader.py @@ -0,0 +1,31 @@ +import os.path +from unittest.mock import Mock + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + +from topics.models import GlossaryTerm +from topics.management.commands._GlossaryTermsLoader import GlossaryTermsLoader + + +class GlossaryTermsLoaderTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = TopicsTestDataGenerator() + self.config_file = Mock() + self.loader_name = "glossary_terms" + self.BASE_PATH = os.path.join(self.test_data.LOADER_ASSET_PATH, self.loader_name) + + def test_basic_config(self): + folder = "glossary_folder" + + glossary_loader = GlossaryTermsLoader(folder, self.config_file, self.BASE_PATH) + glossary_loader.load() + + glossary_objects = GlossaryTerm.objects.all() + + self.assertQuerysetEqual( + glossary_objects, + [""] + ) diff --git a/csunplugged/tests/topics/loaders/test_learning_outcomes_loader.py b/csunplugged/tests/topics/loaders/test_learning_outcomes_loader.py new file mode 100644 index 000000000..602ec34e7 --- /dev/null +++ b/csunplugged/tests/topics/loaders/test_learning_outcomes_loader.py @@ -0,0 +1,29 @@ +import os.path + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + +from topics.models import LearningOutcome +from topics.management.commands._LearningOutcomesLoader import LearningOutcomesLoader + + +class LearningOutcomesLoaderTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = TopicsTestDataGenerator() + self.loader_name = "learning_outcomes" + self.BASE_PATH = os.path.join(self.test_data.LOADER_ASSET_PATH, self.loader_name) + + def test_basic_config(self): + config_file = "basic-config.yaml" + + lo_loader = LearningOutcomesLoader(config_file, self.BASE_PATH) + lo_loader.load() + + lo_objects = LearningOutcome.objects.all() + + self.assertQuerysetEqual( + lo_objects, + [""] + ) diff --git a/csunplugged/tests/topics/loaders/test_lessons_loader.py b/csunplugged/tests/topics/loaders/test_lessons_loader.py new file mode 100644 index 000000000..69231e99e --- /dev/null +++ b/csunplugged/tests/topics/loaders/test_lessons_loader.py @@ -0,0 +1,535 @@ +import os.path + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + +from topics.models import Lesson +from topics.management.commands._LessonsLoader import LessonsLoader + +from utils.errors.CouldNotFindConfigFileError import CouldNotFindConfigFileError +from utils.errors.EmptyConfigFileError import EmptyConfigFileError +from utils.errors.EmptyMarkdownFileError import EmptyMarkdownFileError +from utils.errors.KeyNotFoundError import KeyNotFoundError +from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError +from utils.errors.NoHeadingFoundInMarkdownFileError import NoHeadingFoundInMarkdownFileError + + +class LessonsLoaderTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = TopicsTestDataGenerator() + self.loader_name = "lessons" + + def test_basic_lesson_loader_configuration(self): + config_file = os.path.join(self.loader_name, "basic-config.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + + self.assertQuerysetEqual( + Lesson.objects.all(), + [""] + ) + + def test_lesson_loader_topic_set_correctly(self): + config_file = os.path.join(self.loader_name, "basic-config.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + self.assertEquals( + Lesson.objects.get(slug="lesson-1").topic, + topic, + ) + + # Note: Missing topic does not need to be tested as the topic loader + # is a parent loader to the lesson loader. Therefore this loader, + # is not run if the topic cannot be found. + + def test_lesson_loader_unit_plan_set_correctly(self): + config_file = os.path.join(self.loader_name, "basic-config.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + self.assertEquals( + Lesson.objects.get(slug="lesson-1").unit_plan, + unit_plan, + ) + + # Note: Missing unit plan does not need to be tested as the unit plan loader + # is a parent loader to the lesson loader. Therefore this loader, + # is not run if the unit plan cannot be found. + + def test_lesson_loader_slug_set_correctly(self): + config_file = os.path.join(self.loader_name, "basic-config.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + self.assertEquals( + Lesson.objects.get(slug="lesson-1").slug, + "lesson-1", + ) + + # Note: Missing slug cannot be tested as this is parsed in the unit plan + # loader. + + def test_lesson_loader_name_set_correctly(self): + config_file = os.path.join(self.loader_name, "basic-config.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + self.assertEquals( + Lesson.objects.get(slug="lesson-1").name, + "Lesson 1", + ) + + def test_lesson_loader_missing_name_text(self): + config_file = os.path.join(self.loader_name, "missing-title.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + self.assertRaises( + NoHeadingFoundInMarkdownFileError, + lesson_loader.load, + ) + + def test_lesson_loader_content_set_correctly(self): + config_file = os.path.join(self.loader_name, "basic-config.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + self.assertEquals( + Lesson.objects.get(slug="lesson-1").content, + "

Etiam in massa. Nam ut metus. In rhoncus venenatis tellus.

", + ) + + def test_lesson_loader_missing_content_text(self): + config_file = os.path.join(self.loader_name, "missing-content.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + self.assertRaises( + EmptyMarkdownFileError, + lesson_loader.load, + ) + + def test_lesson_loader_valid_computational_thinking_content(self): + config_file = os.path.join(self.loader_name, "ct-links.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + self.assertEquals( + Lesson.objects.get(slug="ct-links").computational_thinking_links, + "

Example text for Computational Thinking links.

", + ) + + def test_lesson_loader_missing_computational_thinking_content(self): + config_file = os.path.join(self.loader_name, "basic-config.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + self.assertIsNone(Lesson.objects.get(slug="lesson-1").computational_thinking_links) + + def test_lesson_loader_duration_set_correctly(self): + config_file = os.path.join(self.loader_name, "duration.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + + self.assertQuerysetEqual( + Lesson.objects.all(), + [""] + ) + self.assertEquals( + Lesson.objects.get(slug="lesson-1").duration, + 60, + ) + + def test_lesson_loader_heading_tree_set_correctly(self): + config_file = os.path.join(self.loader_name, "heading-tree.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + self.assertEquals( + Lesson.objects.get(slug="lesson-2").heading_tree, + [ + { + 'level': 2, + 'text': 'Heading 2', + 'slug': 'heading-2', + 'children': [ + { + 'level': 3, + 'text': 'Heading 3', + 'slug': 'heading-3', + 'children': [], + } + ], + }, + { + 'level': 2, + 'text': 'Heading 4', + 'slug': 'heading-4', + 'children': [], + } + ], + ) + + def test_lesson_loader_no_heading_tree(self): + config_file = os.path.join(self.loader_name, "basic-config.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + self.assertIsNone(Lesson.objects.get(slug="lesson-1").heading_tree) + + def test_lesson_loader_optional_programming_challenges_set_correctly(self): + config_file = os.path.join(self.loader_name, "programming-challenges.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + difficulty = self.test_data.create_difficulty_level(1) + self.test_data.create_programming_challenge(topic, 1, difficulty) + self.test_data.create_programming_challenge(topic, 2, difficulty) + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + self.assertQuerysetEqual( + Lesson.objects.get(slug="lesson-1").programming_challenges.all(), + [ + "", + "", + ], + ordered=False, + ) + + def test_lesson_loader_optional_programming_challenges_invalid_slug(self): + config_file = os.path.join(self.loader_name, "programming-challenges-invalid.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + self.assertRaises( + KeyNotFoundError, + lesson_loader.load, + ) + + def test_lesson_loader_optional_programming_challenges_set_correctly_when_omitted(self): + config_file = os.path.join(self.loader_name, "basic-config.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + difficulty = self.test_data.create_difficulty_level(1) + self.test_data.create_programming_challenge(topic, 1, difficulty) + self.test_data.create_programming_challenge(topic, 2, difficulty) + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + self.assertQuerysetEqual( + Lesson.objects.get(slug="lesson-1").programming_challenges.all(), + [], + ) + + def test_lesson_loader_valid_programming_challenges_description(self): + config_file = os.path.join(self.loader_name, "programming-challenges-description.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + self.assertEquals( + Lesson.objects.get(slug="programming-challenges-description").programming_challenges_description, + "

Description of lesson programming challenges.

", + ) + + def test_lesson_loader_missing_programming_challenges_description(self): + config_file = os.path.join(self.loader_name, "basic-config.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + self.assertIsNone(Lesson.objects.get(slug="lesson-1").programming_challenges_description) + + def test_lesson_loader_optional_learning_outcomes_set_correctly(self): + config_file = os.path.join(self.loader_name, "learning-outcomes.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + self.test_data.create_learning_outcome(1) + self.test_data.create_learning_outcome(2) + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + self.assertQuerysetEqual( + Lesson.objects.get(slug="lesson-1").learning_outcomes.all(), + [ + "", + "", + ], + ordered=False, + ) + + def test_lesson_loader_optional_learning_outcomes_set_correctly_when_omitted(self): + config_file = os.path.join(self.loader_name, "basic-config.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + self.test_data.create_learning_outcome(1) + self.test_data.create_learning_outcome(2) + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + self.assertQuerysetEqual( + Lesson.objects.get(slug="lesson-1").learning_outcomes.all(), + [], + ) + + def test_lesson_loader_optional_classroom_resources_set_correctly_when_omitted(self): + config_file = os.path.join(self.loader_name, "basic-config.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + self.assertEqual( + Lesson.objects.get(slug="lesson-1").classroom_resources, + None, + ) + + def test_lesson_loader_multiple_lessons(self): + config_file = os.path.join(self.loader_name, "multiple-lessons.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + lesson_loader.load() + self.assertQuerysetEqual( + Lesson.objects.all(), + [ + "", + "", + "", + ], + ordered=False, + ) + + def test_lessons_loader_missing_configuration_file(self): + config_file = os.path.join(self.loader_name, "missing.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + self.assertRaises( + CouldNotFindConfigFileError, + lesson_loader.load, + ) + + def test_lessons_loader_empty_configuration_file(self): + config_file = os.path.join(self.loader_name, "empty.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + self.assertRaises( + EmptyConfigFileError, + lesson_loader.load, + ) + + def test_lessons_loader_missing_lesson_data(self): + config_file = os.path.join(self.loader_name, "missing-lesson-data.yaml") + lessons_structure = os.path.join(self.test_data.LOADER_ASSET_PATH, config_file) + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "1") + lesson_loader = LessonsLoader( + lessons_structure, + topic, + unit_plan, + self.test_data.LOADER_ASSET_PATH + ) + self.assertRaises( + MissingRequiredFieldError, + lesson_loader.load, + ) diff --git a/csunplugged/tests/topics/loaders/test_programming_challenges_loader.py b/csunplugged/tests/topics/loaders/test_programming_challenges_loader.py new file mode 100644 index 000000000..59a9b2c7a --- /dev/null +++ b/csunplugged/tests/topics/loaders/test_programming_challenges_loader.py @@ -0,0 +1,32 @@ +import os.path + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + +from topics.models import ProgrammingChallenge +from topics.management.commands._ProgrammingChallengesLoader import ProgrammingChallengesLoader + + +class ProgrammingChallengesLoaderTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = TopicsTestDataGenerator() + self.loader_name = "programming_challenges" + + def test_basic_config(self): + config_file = os.path.join(self.loader_name, "basic-config.yaml") + + self.test_data.create_difficulty_level("1") + self.test_data.create_programming_language("1") + topic = self.test_data.create_topic("1") + + pe_loader = ProgrammingChallengesLoader(config_file, topic, self.test_data.LOADER_ASSET_PATH) + pe_loader.load() + + pe_objects = ProgrammingChallenge.objects.all() + + self.assertQuerysetEqual( + pe_objects, + [""] + ) diff --git a/csunplugged/tests/topics/loaders/test_programming_challenges_structure_loader.py b/csunplugged/tests/topics/loaders/test_programming_challenges_structure_loader.py new file mode 100644 index 000000000..877d8c07b --- /dev/null +++ b/csunplugged/tests/topics/loaders/test_programming_challenges_structure_loader.py @@ -0,0 +1,36 @@ +import os.path + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + +from topics.models import ProgrammingChallengeDifficulty +from topics.models import ProgrammingChallengeLanguage +from topics.management.commands._ProgrammingChallengesStructureLoader import ProgrammingChallengesStructureLoader + + +class ProgrammingChallengesStructureLoaderTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = TopicsTestDataGenerator() + self.loader_name = "programming_challenges_structure" + self.BASE_PATH = os.path.join(self.test_data.LOADER_ASSET_PATH, self.loader_name) + + def test_basic_config(self): + config_file = "basic-config.yaml" + + pes_loader = ProgrammingChallengesStructureLoader(config_file, self.BASE_PATH) + pes_loader.load() + + ped_objects = ProgrammingChallengeDifficulty.objects.all() + pel_objects = ProgrammingChallengeLanguage.objects.all() + + self.assertQuerysetEqual( + ped_objects, + [""] + ) + + self.assertQuerysetEqual( + pel_objects, + [""] + ) diff --git a/csunplugged/tests/topics/loaders/test_topic_loader.py b/csunplugged/tests/topics/loaders/test_topic_loader.py new file mode 100644 index 000000000..5d73eef2b --- /dev/null +++ b/csunplugged/tests/topics/loaders/test_topic_loader.py @@ -0,0 +1,155 @@ +import os.path +from unittest.mock import Mock + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + +from topics.models import Topic +from topics.management.commands._TopicLoader import TopicLoader + +from utils.errors.CouldNotFindConfigFileError import CouldNotFindConfigFileError +from utils.errors.NoHeadingFoundInMarkdownFileError import NoHeadingFoundInMarkdownFileError +from utils.errors.EmptyMarkdownFileError import EmptyMarkdownFileError +from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError + + +class TopicLoaderTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = TopicsTestDataGenerator() + self.loader_name = "topic" + self.BASE_PATH = os.path.join(self.test_data.LOADER_ASSET_PATH, self.loader_name) + + def test_basic_topic_loader_configuration(self): + config_file = "topic-1/topic-1.yaml" + factory = Mock() + topic_loader = TopicLoader( + factory, + config_file, + self.BASE_PATH + ) + topic_loader.load() + self.assertQuerysetEqual( + Topic.objects.all(), + [""] + ) + + def test_topic_loader_slug(self): + config_file = "topic-1/topic-1.yaml" + factory = Mock() + topic_loader = TopicLoader( + factory, + config_file, + self.BASE_PATH + ) + topic_loader.load() + self.assertEquals( + Topic.objects.get(slug="topic-1").slug, + "topic-1", + ) + + def test_topic_loader_missing_configuration_file(self): + config_file = "topic-1/topic-missing.yaml" + factory = Mock() + topic_loader = TopicLoader( + factory, + config_file, + self.BASE_PATH + ) + self.assertRaises( + CouldNotFindConfigFileError, + topic_loader.load, + ) + + def test_topic_loader_valid_name_text(self): + config_file = "topic-1/topic-1.yaml" + factory = Mock() + topic_loader = TopicLoader( + factory, + config_file, + self.BASE_PATH + ) + topic_loader.load() + self.assertEquals( + Topic.objects.get(slug="topic-1").name, + "Topic 1", + ) + + def test_topic_loader_missing_name_text(self): + config_file = "topic-missing-name/topic-missing-name.yaml" + factory = Mock() + topic_loader = TopicLoader( + factory, + config_file, + self.BASE_PATH + ) + self.assertRaises( + NoHeadingFoundInMarkdownFileError, + topic_loader.load, + ) + + def test_topic_loader_valid_content_text(self): + config_file = "topic-1/topic-1.yaml" + factory = Mock() + topic_loader = TopicLoader( + factory, + config_file, + self.BASE_PATH + ) + topic_loader.load() + self.assertEquals( + Topic.objects.get(slug="topic-1").content, + "

Etiam in massa. Nam ut metus. In rhoncus venenatis tellus.

", + ) + + def test_topic_loader_missing_content_text(self): + config_file = "topic-missing-content/topic-missing-content.yaml" + factory = Mock() + topic_loader = TopicLoader( + factory, + config_file, + self.BASE_PATH + ) + self.assertRaises( + EmptyMarkdownFileError, + topic_loader.load, + ) + + def test_topic_loader_missing_unit_plans(self): + config_file = "topic-missing-unit-plans/topic-missing-unit-plans.yaml" + factory = Mock() + topic_loader = TopicLoader( + factory, + config_file, + self.BASE_PATH + ) + self.assertRaises( + MissingRequiredFieldError, + topic_loader.load, + ) + + def test_topic_loader_valid_icon(self): + config_file = "topic-valid-icon/topic-valid-icon.yaml" + factory = Mock() + topic_loader = TopicLoader( + factory, + config_file, + self.BASE_PATH + ) + topic_loader.load() + self.assertEquals( + Topic.objects.get(slug="topic-valid-icon").icon, + "img/logo.png", + ) + + def test_topic_loader_missing_icon(self): + config_file = "topic-1/topic-1.yaml" + factory = Mock() + topic_loader = TopicLoader( + factory, + config_file, + self.BASE_PATH + ) + # Passes if loader throws no exception + topic_loader.load() diff --git a/csunplugged/tests/topics/loaders/test_unit_plan_loader.py b/csunplugged/tests/topics/loaders/test_unit_plan_loader.py new file mode 100644 index 000000000..22d452e5a --- /dev/null +++ b/csunplugged/tests/topics/loaders/test_unit_plan_loader.py @@ -0,0 +1,166 @@ +import os.path +from unittest.mock import Mock + +from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError +from utils.errors.EmptyMarkdownFileError import EmptyMarkdownFileError +from utils.errors.NoHeadingFoundInMarkdownFileError import NoHeadingFoundInMarkdownFileError + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + +from topics.models import UnitPlan +from topics.management.commands._UnitPlanLoader import UnitPlanLoader + + +class UnitPlanLoaderTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = TopicsTestDataGenerator() + self.loader_name = "unit_plan" + self.BASE_PATH = os.path.join(self.test_data.LOADER_ASSET_PATH, self.loader_name) + + def test_basic_unit_plan_configuration(self): + config_file = "unit-plan-1/unit-plan-1.yaml" + + # create test objects so that lesson exist for age group + factory = Mock() + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "test") + self.test_data.create_lesson(topic, unit_plan, "1") + self.test_data.create_age_group(8, 10) + + up_loader = UnitPlanLoader(factory, config_file, topic, self.BASE_PATH) + up_loader.load() + + up_objects = UnitPlan.objects.all() + + self.assertQuerysetEqual( + up_objects, + [ + "", + "" + ], + ordered=False + ) + + def test_unit_plan_loader_missing_lessons_config_value(self): + config_file = "unit-plan-1/missing-lessons-config.yaml" + + factory = Mock() + topic = self.test_data.create_topic("1") + + up_loader = UnitPlanLoader(factory, config_file, topic, self.BASE_PATH) + + self.assertRaises( + MissingRequiredFieldError, + up_loader.load, + ) + + def test_unit_plan_loader_missing_content_text(self): + config_file = "missing-content/missing-content.yaml" + + factory = Mock() + topic = self.test_data.create_topic("1") + + up_loader = UnitPlanLoader(factory, config_file, topic, self.BASE_PATH) + + self.assertRaises( + EmptyMarkdownFileError, + up_loader.load, + ) + + def test_unit_plan_loader_missing_name_text(self): + config_file = "missing-name/missing-name.yaml" + + factory = Mock() + topic = self.test_data.create_topic("1") + + up_loader = UnitPlanLoader(factory, config_file, topic, self.BASE_PATH) + + self.assertRaises( + NoHeadingFoundInMarkdownFileError, + up_loader.load, + ) + + def test_unit_plan_missing_age_groups(self): + config_file = "unit-plan-1/missing-age-groups.yaml" + + factory = Mock() + topic = self.test_data.create_topic("1") + + up_loader = UnitPlanLoader(factory, config_file, topic, self.BASE_PATH) + + self.assertRaises( + MissingRequiredFieldError, + up_loader.load, + ) + + def test_unit_plan_loader_valid_computational_thinking_content(self): + config_file = "ct-links/ct-links.yaml" + + factory = Mock() + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "test") + self.test_data.create_lesson(topic, unit_plan, "1") + self.test_data.create_age_group(8, 10) + + up_loader = UnitPlanLoader(factory, config_file, topic, self.BASE_PATH) + up_loader.load() + self.assertEqual( + UnitPlan.objects.get(slug="ct-links").computational_thinking_links, + "

CT link text

" + ) + + def test_unit_plan_loader_missing_computational_thinking_content(self): + config_file = "unit-plan-1/unit-plan-1.yaml" + + factory = Mock() + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "test") + self.test_data.create_lesson(topic, unit_plan, "1") + self.test_data.create_age_group(8, 10) + + up_loader = UnitPlanLoader(factory, config_file, topic, self.BASE_PATH) + up_loader.load() + + up_objects = UnitPlan.objects.all() + + self.assertQuerysetEqual( + up_objects, + [ + "", + "" + ], + ordered=False + ) + + def test_unit_plan_missing_lesson_keys(self): + config_file = "unit-plan-1/missing-lesson-keys.yaml" + + factory = Mock() + topic = self.test_data.create_topic("1") + self.test_data.create_age_group(8, 10) + + up_loader = UnitPlanLoader(factory, config_file, topic, self.BASE_PATH) + + self.assertRaises( + MissingRequiredFieldError, + up_loader.load, + ) + + def test_unit_plan_missing_lesson_number(self): + config_file = "unit-plan-1/missing-lesson-number.yaml" + + factory = Mock() + topic = self.test_data.create_topic("1") + unit_plan = self.test_data.create_unit_plan(topic, "test") + self.test_data.create_lesson(topic, unit_plan, "1") + self.test_data.create_age_group(8, 10) + + up_loader = UnitPlanLoader(factory, config_file, topic, self.BASE_PATH) + + self.assertRaises( + MissingRequiredFieldError, + up_loader.load, + ) diff --git a/csunplugged/tests/topics/models/__init__.py b/csunplugged/tests/topics/models/__init__.py new file mode 100644 index 000000000..5a1def518 --- /dev/null +++ b/csunplugged/tests/topics/models/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the models in the topics application.""" diff --git a/csunplugged/tests/topics/models/test_curriculum_area.py b/csunplugged/tests/topics/models/test_curriculum_area.py new file mode 100644 index 000000000..d004877a5 --- /dev/null +++ b/csunplugged/tests/topics/models/test_curriculum_area.py @@ -0,0 +1,43 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from topics.models import CurriculumArea + + +class CurriculumAreaModelTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def test_curriculum_area(self): + new_curriculum_area = CurriculumArea.objects.create( + slug="slug", + name="name", + colour="red", + number=1, + ) + query_result = CurriculumArea.objects.get(slug="slug") + self.assertEqual(query_result, new_curriculum_area) + + def test_curriculum_area_str(self): + new_curriculum_area = CurriculumArea.objects.create( + slug="slug", + name="name", + colour="red", + number=1, + ) + self.assertEqual(new_curriculum_area.__str__(), "name") + + def test_curriculum_area_str_as_child(self): + parent_curriculum_area = CurriculumArea.objects.create( + slug="parent-slug", + name="parent name", + colour="red", + number=1, + ) + child_curriculum_area = CurriculumArea.objects.create( + slug="child-slug", + name="child name", + colour="red", + number=parent_curriculum_area.number, + parent=parent_curriculum_area, + ) + self.assertEqual(child_curriculum_area.__str__(), "parent name: child name") diff --git a/csunplugged/tests/topics/models/test_curriculum_integration.py b/csunplugged/tests/topics/models/test_curriculum_integration.py new file mode 100644 index 000000000..cec33a950 --- /dev/null +++ b/csunplugged/tests/topics/models/test_curriculum_integration.py @@ -0,0 +1,36 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + +from topics.models import CurriculumIntegration + + +class CurriculumIntegrationModelTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = TopicsTestDataGenerator() + + def test_curriculum_integration(self): + # Setup Auxliary Data + topic = self.test_data.create_topic(1) + curriculum_area = self.test_data.create_curriculum_area(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + age_group_1 = self.test_data.create_age_group(1, 99) + prerequisite_lesson = self.test_data.create_lesson( + topic, + unit_plan, + 1, + age_group_1 + ) + new_curriculum_integration = CurriculumIntegration.objects.create( + topic=topic, + slug="slug", + number=1, + name="name", + content="content" + ) + new_curriculum_integration.curriculum_areas.add(curriculum_area) + new_curriculum_integration.prerequisite_lessons.add(prerequisite_lesson) + + query_result = CurriculumIntegration.objects.get(slug="slug") + self.assertEqual(query_result, new_curriculum_integration) diff --git a/csunplugged/tests/topics/models/test_learning_outcome.py b/csunplugged/tests/topics/models/test_learning_outcome.py new file mode 100644 index 000000000..fb5c8cd79 --- /dev/null +++ b/csunplugged/tests/topics/models/test_learning_outcome.py @@ -0,0 +1,7 @@ +from tests.BaseTestWithDB import BaseTestWithDB + + +class LearningOutcomeModelTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) diff --git a/csunplugged/tests/topics/models/test_lesson.py b/csunplugged/tests/topics/models/test_lesson.py new file mode 100644 index 000000000..c2b0e25df --- /dev/null +++ b/csunplugged/tests/topics/models/test_lesson.py @@ -0,0 +1,7 @@ +from tests.BaseTestWithDB import BaseTestWithDB + + +class LessonModelTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) diff --git a/csunplugged/tests/topics/models/test_programming_challenge.py b/csunplugged/tests/topics/models/test_programming_challenge.py new file mode 100644 index 000000000..8b661df13 --- /dev/null +++ b/csunplugged/tests/topics/models/test_programming_challenge.py @@ -0,0 +1,7 @@ +from tests.BaseTestWithDB import BaseTestWithDB + + +class ProgrammingChallengeModelTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) diff --git a/csunplugged/tests/topics/models/test_programming_challenge_difficulty.py b/csunplugged/tests/topics/models/test_programming_challenge_difficulty.py new file mode 100644 index 000000000..2b955e9ed --- /dev/null +++ b/csunplugged/tests/topics/models/test_programming_challenge_difficulty.py @@ -0,0 +1,7 @@ +from tests.BaseTestWithDB import BaseTestWithDB + + +class ProgrammingChallengeDifficultyModelTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) diff --git a/csunplugged/tests/topics/models/test_programming_challenge_implementation.py b/csunplugged/tests/topics/models/test_programming_challenge_implementation.py new file mode 100644 index 000000000..e0a0d5c0c --- /dev/null +++ b/csunplugged/tests/topics/models/test_programming_challenge_implementation.py @@ -0,0 +1,78 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator +from topics.models import ProgrammingChallenge + + +class ProgrammingChallengeImplementationModelTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_data = TopicsTestDataGenerator() + + def test_implementation(self): + topic = self.test_data.create_topic(1) + difficulty = self.test_data.create_difficulty_level(1) + language = self.test_data.create_programming_language(1) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + implementation = self.test_data.create_programming_challenge_implementation( + topic=topic, + language=language, + challenge=challenge + ) + self.assertEqual( + ProgrammingChallenge.objects.get(slug="challenge-1").implementations.get(language__slug="language-1"), + implementation + ) + + def test_implementation_str(self): + topic = self.test_data.create_topic(1) + difficulty = self.test_data.create_difficulty_level(1) + language = self.test_data.create_programming_language(1) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + implementation = self.test_data.create_programming_challenge_implementation( + topic=topic, + language=language, + challenge=challenge, + ) + self.assertEqual( + implementation.__str__(), + "Language 1 for challenge 1.1, Challenge 1.1: 1" + ) + + def test_implementation_order(self): + topic = self.test_data.create_topic(1) + difficulty = self.test_data.create_difficulty_level(1) + language_1 = self.test_data.create_programming_language(1) + language_2 = self.test_data.create_programming_language(2) + language_3 = self.test_data.create_programming_language(3) + language_4 = self.test_data.create_programming_language(4) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + self.test_data.create_programming_challenge_implementation( + topic=topic, + language=language_4, + challenge=challenge, + ) + self.test_data.create_programming_challenge_implementation( + topic=topic, + language=language_1, + challenge=challenge, + ) + self.test_data.create_programming_challenge_implementation( + topic=topic, + language=language_3, + challenge=challenge, + ) + self.test_data.create_programming_challenge_implementation( + topic=topic, + language=language_2, + challenge=challenge, + ) + self.assertQuerysetEqual( + ProgrammingChallenge.objects.get(slug="challenge-1").ordered_implementations(), + [ + "", + "", + "", + "", + ] + ) diff --git a/csunplugged/tests/topics/models/test_programming_challenge_language.py b/csunplugged/tests/topics/models/test_programming_challenge_language.py new file mode 100644 index 000000000..3caec46ab --- /dev/null +++ b/csunplugged/tests/topics/models/test_programming_challenge_language.py @@ -0,0 +1,7 @@ +from tests.BaseTestWithDB import BaseTestWithDB + + +class ProgrammingChallengeLanguageModelTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) diff --git a/csunplugged/tests/topics/models/test_resource_description.py b/csunplugged/tests/topics/models/test_resource_description.py new file mode 100644 index 000000000..ed931d9e9 --- /dev/null +++ b/csunplugged/tests/topics/models/test_resource_description.py @@ -0,0 +1,31 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator +from tests.resources.ResourcesTestDataGenerator import ResourcesTestDataGenerator +from topics.models import ResourceDescription + + +class ResourceDescriptionModelTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.test_topics_data = TopicsTestDataGenerator() + self.test_resources_data = ResourcesTestDataGenerator() + + def test_connected_generated_resource(self): + resource = self.test_resources_data.create_resource( + "binary-cards", + "Binary Cards", + "resources/binary-cards.html", + "binary_cards.py", + ) + topic = self.test_topics_data.create_topic(1) + unit_plan = self.test_topics_data.create_unit_plan(topic, 1) + age_group = self.test_topics_data.create_age_group(1, 99) + lesson = self.test_topics_data.create_lesson(topic, unit_plan, 1, age_group) + new_resource = ResourceDescription.objects.create( + resource=resource, + lesson=lesson, + description="this is a description" + ) + query_result = ResourceDescription.objects.get(resource=resource, lesson=lesson) + self.assertEqual(query_result, new_resource) diff --git a/csunplugged/tests/topics/models/test_topic.py b/csunplugged/tests/topics/models/test_topic.py new file mode 100644 index 000000000..2294425cd --- /dev/null +++ b/csunplugged/tests/topics/models/test_topic.py @@ -0,0 +1,19 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from topics.models import Topic + + +class TopicModelTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def test_topic(self): + new_topic = Topic.objects.create( + slug="binary-numbers", + name="Binary Numbers", + content="content", + other_resources="content", + icon="icon" + ) + query_result = Topic.objects.get(name="Binary Numbers") + self.assertEqual(query_result, new_topic) diff --git a/csunplugged/tests/topics/models/test_unit_plan.py b/csunplugged/tests/topics/models/test_unit_plan.py new file mode 100644 index 000000000..595a3189d --- /dev/null +++ b/csunplugged/tests/topics/models/test_unit_plan.py @@ -0,0 +1,7 @@ +from tests.BaseTestWithDB import BaseTestWithDB + + +class UnitPlanModelTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) diff --git a/csunplugged/tests/topics/urls/__init__.py b/csunplugged/tests/topics/urls/__init__.py new file mode 100644 index 000000000..7b9644038 --- /dev/null +++ b/csunplugged/tests/topics/urls/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the URL routing in the topics application.""" diff --git a/csunplugged/tests/topics/urls/test_all_curriculum_integrations.py b/csunplugged/tests/topics/urls/test_all_curriculum_integrations.py new file mode 100644 index 000000000..5ecbfaa84 --- /dev/null +++ b/csunplugged/tests/topics/urls/test_all_curriculum_integrations.py @@ -0,0 +1,13 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class AllCurriculumIntegrationsURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_curriculum_integration(self): + url = reverse("topics:all_curriculum_integrations") + self.assertEqual(url, "/en/topics/curriculum-integrations/") diff --git a/csunplugged/tests/topics/urls/test_glossary_url.py b/csunplugged/tests/topics/urls/test_glossary_url.py new file mode 100644 index 000000000..0f2335059 --- /dev/null +++ b/csunplugged/tests/topics/urls/test_glossary_url.py @@ -0,0 +1,17 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class GlossaryURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_glossary_url(self): + url = reverse("topics:glossary") + self.assertEqual(url, "/en/topics/glossary/") + + def test_valid_glossary_json_url(self): + url = reverse("topics:glossary_json") + self.assertEqual(url, "/en/topics/glossary/json/") diff --git a/csunplugged/tests/topics/urls/test_index.py b/csunplugged/tests/topics/urls/test_index.py new file mode 100644 index 000000000..6a7db5447 --- /dev/null +++ b/csunplugged/tests/topics/urls/test_index.py @@ -0,0 +1,16 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class IndexURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_index(self): + url = reverse("topics:index") + self.assertEqual(url, "/en/topics/") + + response = self.client.get(url) + self.assertEqual(200, response.status_code) diff --git a/csunplugged/tests/topics/urls/test_integration.py b/csunplugged/tests/topics/urls/test_integration.py new file mode 100644 index 000000000..76c1ecaa0 --- /dev/null +++ b/csunplugged/tests/topics/urls/test_integration.py @@ -0,0 +1,13 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class IntegrationURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_integration(self): + url = reverse("topics:integration", args=["binary-numbers", "binary-bracelets"]) + self.assertEqual(url, "/en/topics/binary-numbers/integrations/binary-bracelets/") diff --git a/csunplugged/tests/topics/urls/test_lesson.py b/csunplugged/tests/topics/urls/test_lesson.py new file mode 100644 index 000000000..fa713f6de --- /dev/null +++ b/csunplugged/tests/topics/urls/test_lesson.py @@ -0,0 +1,13 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class LessonURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_lesson(self): + url = reverse("topics:lesson", args=["binary-numbers", "unit-plan", "lesson-1"]) + self.assertEqual(url, "/en/topics/binary-numbers/unit-plan/lesson-1/") diff --git a/csunplugged/tests/topics/urls/test_other_resources.py b/csunplugged/tests/topics/urls/test_other_resources.py new file mode 100644 index 000000000..050d9941e --- /dev/null +++ b/csunplugged/tests/topics/urls/test_other_resources.py @@ -0,0 +1,13 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class OtherResourcesURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_other_resources(self): + url = reverse("topics:other_resources", args=["binary-numbers"]) + self.assertEqual(url, "/en/topics/binary-numbers/other-resources/") diff --git a/csunplugged/tests/topics/urls/test_programming_challenge.py b/csunplugged/tests/topics/urls/test_programming_challenge.py new file mode 100644 index 000000000..171dbd8b3 --- /dev/null +++ b/csunplugged/tests/topics/urls/test_programming_challenge.py @@ -0,0 +1,13 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class ProgrammingChallengeURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_programming_challenge(self): + url = reverse("topics:programming_challenge", args=["binary-numbers", "challenge-1"]) + self.assertEqual(url, "/en/topics/binary-numbers/programming/challenge-1") diff --git a/csunplugged/tests/topics/urls/test_programming_challenge_language_solution.py b/csunplugged/tests/topics/urls/test_programming_challenge_language_solution.py new file mode 100644 index 000000000..7471d9ba0 --- /dev/null +++ b/csunplugged/tests/topics/urls/test_programming_challenge_language_solution.py @@ -0,0 +1,15 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class ProgrammingChallengeSolutionURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_programming_challenge_solution(self): + args = ["binary-numbers", "challenge-1", "python"] + url = reverse("topics:programming_challenge_solution", args=args) + expected_url = "/en/topics/binary-numbers/programming/challenge-1/python-solution" + self.assertEqual(url, expected_url) diff --git a/csunplugged/tests/topics/urls/test_programming_challenges_list.py b/csunplugged/tests/topics/urls/test_programming_challenges_list.py new file mode 100644 index 000000000..7c05403f9 --- /dev/null +++ b/csunplugged/tests/topics/urls/test_programming_challenges_list.py @@ -0,0 +1,13 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class ProgrammingChallengesListURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_programming_challenges_list(self): + url = reverse("topics:programming_challenges_list", args=["binary-numbers", "unit-plan", "lesson-1"]) + self.assertEqual(url, "/en/topics/binary-numbers/unit-plan/lesson-1/programming/") diff --git a/csunplugged/tests/topics/urls/test_topic.py b/csunplugged/tests/topics/urls/test_topic.py new file mode 100644 index 000000000..75349fa4a --- /dev/null +++ b/csunplugged/tests/topics/urls/test_topic.py @@ -0,0 +1,13 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class TopicURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_topic(self): + url = reverse("topics:topic", args=["binary-numbers"]) + self.assertEqual(url, "/en/topics/binary-numbers/") diff --git a/csunplugged/tests/topics/urls/test_unit_plan.py b/csunplugged/tests/topics/urls/test_unit_plan.py new file mode 100644 index 000000000..b29bb0536 --- /dev/null +++ b/csunplugged/tests/topics/urls/test_unit_plan.py @@ -0,0 +1,13 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse + + +class UnitPlanURLTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_valid_unit_plan(self): + url = reverse("topics:unit_plan", args=["binary-numbers", "unit-plan"]) + self.assertEqual(url, "/en/topics/binary-numbers/unit-plan/") diff --git a/csunplugged/tests/topics/views/__init__.py b/csunplugged/tests/topics/views/__init__.py new file mode 100644 index 000000000..a369b1d01 --- /dev/null +++ b/csunplugged/tests/topics/views/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the views in the topics application.""" diff --git a/csunplugged/tests/topics/views/test_all_curriculum_integration_view.py b/csunplugged/tests/topics/views/test_all_curriculum_integration_view.py new file mode 100644 index 000000000..43c1fefa5 --- /dev/null +++ b/csunplugged/tests/topics/views/test_all_curriculum_integration_view.py @@ -0,0 +1,76 @@ +from django.urls import reverse + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + + +class AllCurriculumIntegrationViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + self.test_data = TopicsTestDataGenerator() + + def test_all_curriculum_integration_view_with_no_integrations(self): + url = reverse("topics:all_curriculum_integrations") + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual(len(response.context["curriculum_integrations"]), 0) + + def test_all_curriculum_integration_view_with_one_integration(self): + topic = self.test_data.create_topic(1) + self.test_data.create_integration(topic, 1) + + url = reverse("topics:all_curriculum_integrations") + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual(len(response.context["curriculum_integrations"]), 1) + self.assertQuerysetEqual( + response.context["curriculum_integrations"], + [""] + ) + + def test_all_curriculum_integration_view_with_multiple_integration(self): + topic = self.test_data.create_topic(1) + self.test_data.create_integration(topic, 1) + self.test_data.create_integration(topic, 2) + self.test_data.create_integration(topic, 3) + + url = reverse("topics:all_curriculum_integrations") + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual(len(response.context["curriculum_integrations"]), 3) + self.assertQuerysetEqual( + response.context["curriculum_integrations"], + [ + "", + "", + "", + ] + ) + + def test_all_curriculum_integration_view_order(self): + topic_1 = self.test_data.create_topic(1) + topic_2 = self.test_data.create_topic(2) + self.test_data.create_integration(topic_2, 3) + self.test_data.create_integration(topic_2, 2) + self.test_data.create_integration(topic_2, 1) + self.test_data.create_integration(topic_1, 4) + self.test_data.create_integration(topic_1, 5) + self.test_data.create_integration(topic_1, 6) + + url = reverse("topics:all_curriculum_integrations") + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual(len(response.context["curriculum_integrations"]), 6) + self.assertQuerysetEqual( + response.context["curriculum_integrations"], + [ + "", + "", + "", + "", + "", + "", + ] + ) diff --git a/csunplugged/tests/topics/views/test_curriculum_integration_view.py b/csunplugged/tests/topics/views/test_curriculum_integration_view.py new file mode 100644 index 000000000..70b4a6569 --- /dev/null +++ b/csunplugged/tests/topics/views/test_curriculum_integration_view.py @@ -0,0 +1,125 @@ +from django.urls import reverse + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + + +class CurriculumIntegrationViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + self.test_data = TopicsTestDataGenerator() + + def test_curriculum_integration_view_with_valid_slugs(self): + topic = self.test_data.create_topic(1) + self.test_data.create_integration(topic, 1) + kwargs = { + "topic_slug": topic.slug, + "integration_slug": "integration-1", + } + url = reverse("topics:integration", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + + def test_curriculum_integration_view_with_invalid_topic_slug(self): + topic = self.test_data.create_topic(1) + self.test_data.create_integration(topic, 1) + kwargs = { + "topic_slug": "no-slug", + "integration_slug": "integration-1", + } + url = reverse("topics:integration", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_curriculum_integration_view_with_invalid_integration_slug(self): + topic = self.test_data.create_topic(1) + self.test_data.create_integration(topic, 1) + kwargs = { + "topic_slug": topic.slug, + "integration_slug": "integration-2", + } + url = reverse("topics:integration", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_curriculum_integration_view_topic_context(self): + topic = self.test_data.create_topic(1) + self.test_data.create_integration(topic, 1) + kwargs = { + "topic_slug": "topic-1", + "integration_slug": "integration-1", + } + url = reverse("topics:integration", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual( + response.context["topic"], + topic + ) + + def test_curriculum_integration_view_curriculum_areas_context(self): + topic = self.test_data.create_topic(1) + area_1 = self.test_data.create_curriculum_area(1) + area_2 = self.test_data.create_curriculum_area(2) + self.test_data.create_integration( + topic, + 1, + curriculum_areas=[area_1, area_2] + ) + kwargs = { + "topic_slug": "topic-1", + "integration_slug": "integration-1", + } + url = reverse("topics:integration", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual( + len(response.context["integration_curriculum_areas"]), + 2 + ) + self.assertQuerysetEqual( + response.context["integration_curriculum_areas"], + [ + "", + "", + ] + ) + + def test_curriculum_integration_view_prerequisite_lessons_context(self): + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + age_group_1 = self.test_data.create_age_group(5, 7) + lesson_1 = self.test_data.create_lesson( + topic, + unit_plan, + 1, + age_group_1 + ) + lesson_2 = self.test_data.create_lesson( + topic, + unit_plan, + 2, + age_group_1 + ) + self.test_data.create_integration( + topic, + 1, + lessons=[lesson_1, lesson_2] + ) + kwargs = { + "topic_slug": "topic-1", + "integration_slug": "integration-1", + } + url = reverse("topics:integration", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual( + len(response.context["prerequisite_lessons"]), + 2 + ) + self.assertQuerysetEqual( + response.context["prerequisite_lessons"], + [ + "", + "", + ] + ) diff --git a/csunplugged/tests/topics/views/test_glossary_view.py b/csunplugged/tests/topics/views/test_glossary_view.py new file mode 100644 index 000000000..8476eeddc --- /dev/null +++ b/csunplugged/tests/topics/views/test_glossary_view.py @@ -0,0 +1,159 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse +from topics.models import GlossaryTerm + + +class GlossaryViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_glossary_with_no_definitions(self): + url = reverse("topics:glossary") + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual(len(response.context["glossary_terms"]), 0) + + def test_glossary_with_one_definition(self): + term = GlossaryTerm( + slug="algorithm", + term="Algorithms", + definition="

Algorithms definition.

" + ) + term.save() + + url = reverse("topics:glossary") + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual(len(response.context["glossary_terms"]), 1) + self.assertQuerysetEqual( + response.context["glossary_terms"], + [""] + ) + + def test_glossary_with_two_definitions(self): + term1 = GlossaryTerm( + slug="algorithm", + term="Algorithms", + definition="

Algorithms definition.

" + ) + term1.save() + term2 = GlossaryTerm( + slug="pixel", + term="Pixel", + definition="

Pixel definition.

" + ) + term2.save() + + url = reverse("topics:glossary") + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual(len(response.context["glossary_terms"]), 2) + self.assertQuerysetEqual( + response.context["glossary_terms"], + ["", ""] + ) + + def test_glossary_order(self): + term_c = GlossaryTerm( + slug="c", + term="C", + definition="" + ) + term_c.save() + term_b = GlossaryTerm( + slug="b", + term="B", + definition="" + ) + term_b.save() + term_a = GlossaryTerm( + slug="a", + term="A", + definition="" + ) + term_a.save() + + url = reverse("topics:glossary") + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertEqual(len(response.context["glossary_terms"]), 3) + self.assertQuerysetEqual( + response.context["glossary_terms"], + [ + "", + "", + "" + ] + ) + + def test_glossary_json_with_one_definition(self): + term = GlossaryTerm( + slug="algorithm", + term="Algorithms", + definition="

Algorithms definition.

" + ) + term.save() + + url = reverse("topics:glossary_json") + response = self.client.get(url, {"term": "algorithm"}) + self.assertEqual(200, response.status_code) + self.assertJSONEqual( + str(response.content, encoding="utf8"), + { + "definition": "

Algorithms definition.

", + "slug": "algorithm", + "term": "Algorithms" + } + ) + + def test_glossary_json_with_two_definitions(self): + term1 = GlossaryTerm( + slug="algorithm", + term="Algorithms", + definition="

Algorithms definition.

" + ) + term1.save() + term2 = GlossaryTerm( + slug="pixel", + term="Pixel", + definition="

Pixel definition.

" + ) + term2.save() + + url = reverse("topics:glossary_json") + response = self.client.get(url, {"term": "pixel"}) + self.assertEqual(200, response.status_code) + self.assertJSONEqual( + str(response.content, encoding="utf8"), + { + "definition": "

Pixel definition.

", + "slug": "pixel", + "term": "Pixel" + } + ) + + def test_glossary_json_with_invalid_term(self): + term = GlossaryTerm( + slug="algorithm", + term="Algorithms", + definition="

Algorithms definition.

" + ) + term.save() + + url = reverse("topics:glossary_json") + response = self.client.get(url, {"term": "pixel"}) + self.assertEqual(404, response.status_code) + + def test_glossary_json_with_invalid_key(self): + term = GlossaryTerm( + slug="algorithm", + term="Algorithms", + definition="

Algorithms definition.

" + ) + term.save() + + url = reverse("topics:glossary_json") + response = self.client.get(url, {"word": "pixel"}) + self.assertEqual(404, response.status_code) diff --git a/csunplugged/tests/topics/views/test_index_view.py b/csunplugged/tests/topics/views/test_index_view.py new file mode 100644 index 000000000..b88ff258c --- /dev/null +++ b/csunplugged/tests/topics/views/test_index_view.py @@ -0,0 +1,33 @@ +from tests.BaseTestWithDB import BaseTestWithDB +from django.urls import reverse +from topics.models import Topic + + +class IndexViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_index_with_no_topics(self): + url = reverse("topics:index") + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + def test_index_with_one_topic(self): + new_topic = Topic( + slug="binary-numbers", + name="Binary Numbers", + content="content", + other_resources="content", + icon="icon" + ) + new_topic.save() + + url = reverse("topics:index") + response = self.client.get(url) + self.assertEqual(200, response.status_code) + self.assertQuerysetEqual( + response.context["all_topics"], + [""] + ) diff --git a/csunplugged/tests/topics/views/test_lesson_view.py b/csunplugged/tests/topics/views/test_lesson_view.py new file mode 100644 index 000000000..e69de29bb diff --git a/csunplugged/tests/topics/views/test_other_resources_view.py b/csunplugged/tests/topics/views/test_other_resources_view.py new file mode 100644 index 000000000..e69de29bb diff --git a/csunplugged/tests/topics/views/test_programming_challenge_difficulty.py b/csunplugged/tests/topics/views/test_programming_challenge_difficulty.py new file mode 100644 index 000000000..e69de29bb diff --git a/csunplugged/tests/topics/views/test_programming_challenge_list.py b/csunplugged/tests/topics/views/test_programming_challenge_list.py new file mode 100644 index 000000000..7cbabbd11 --- /dev/null +++ b/csunplugged/tests/topics/views/test_programming_challenge_list.py @@ -0,0 +1,291 @@ +from django.urls import reverse + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + + +class ProgrammingChallengeListTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + self.test_data = TopicsTestDataGenerator() + + def test_programming_challenge_list_with_valid_slugs(self): + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + age_group_1 = self.test_data.create_age_group(5, 7) + lesson = self.test_data.create_lesson( + topic, + unit_plan, + 1, + age_group_1 + ) + difficulty = self.test_data.create_difficulty_level(1) + challenge1 = self.test_data.create_programming_challenge(topic, 1, difficulty) + challenge2 = self.test_data.create_programming_challenge(topic, 2, difficulty) + self.test_data.add_challenge_lesson_relationship( + challenge1, + lesson, + 1, + 1 + ) + self.test_data.add_challenge_lesson_relationship( + challenge2, + lesson, + 1, + 2 + ) + kwargs = { + "topic_slug": topic.slug, + "unit_plan_slug": unit_plan.slug, + "lesson_slug": lesson.slug, + } + url = reverse("topics:programming_challenges_list", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + + def test_programming_challenge_list_with_invalid_topic_slug(self): + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + age_group_1 = self.test_data.create_age_group(5, 7) + lesson = self.test_data.create_lesson( + topic, + unit_plan, + 1, + age_group_1 + ) + difficulty = self.test_data.create_difficulty_level(1) + challenge1 = self.test_data.create_programming_challenge(topic, 1, difficulty) + challenge2 = self.test_data.create_programming_challenge(topic, 2, difficulty) + self.test_data.add_challenge_lesson_relationship( + challenge1, + lesson, + 1, + 1 + ) + self.test_data.add_challenge_lesson_relationship( + challenge2, + lesson, + 1, + 2 + ) + kwargs = { + "topic_slug": "wrong_slug", + "unit_plan_slug": unit_plan.slug, + "lesson_slug": lesson.slug, + } + url = reverse("topics:programming_challenges_list", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_programming_challenge_list_with_invalid_unit_plan_slug(self): + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + age_group_1 = self.test_data.create_age_group(5, 7) + lesson = self.test_data.create_lesson( + topic, + unit_plan, + 1, + age_group_1 + ) + difficulty = self.test_data.create_difficulty_level(1) + challenge1 = self.test_data.create_programming_challenge(topic, 1, difficulty) + challenge2 = self.test_data.create_programming_challenge(topic, 2, difficulty) + self.test_data.add_challenge_lesson_relationship( + challenge1, + lesson, + 1, + 1 + ) + self.test_data.add_challenge_lesson_relationship( + challenge2, + lesson, + 1, + 2 + ) + kwargs = { + "topic_slug": topic.slug, + "unit_plan_slug": "wrong_slug", + "lesson_slug": lesson.slug, + } + url = reverse("topics:programming_challenges_list", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_programming_challenge_list_with_invalid_lesson_slug(self): + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + age_group_1 = self.test_data.create_age_group(5, 7) + lesson = self.test_data.create_lesson( + topic, + unit_plan, + 1, + age_group_1 + ) + difficulty = self.test_data.create_difficulty_level(1) + challenge1 = self.test_data.create_programming_challenge(topic, 1, difficulty) + challenge2 = self.test_data.create_programming_challenge(topic, 2, difficulty) + self.test_data.add_challenge_lesson_relationship( + challenge1, + lesson, + 1, + 1 + ) + self.test_data.add_challenge_lesson_relationship( + challenge2, + lesson, + 1, + 2 + ) + kwargs = { + "topic_slug": topic.slug, + "unit_plan_slug": unit_plan.slug, + "lesson_slug": "wrong_slug", + } + url = reverse("topics:programming_challenges_list", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_programming_challenge_list_topic_context(self): + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + age_group_1 = self.test_data.create_age_group(5, 7) + lesson = self.test_data.create_lesson( + topic, + unit_plan, + 1, + age_group_1 + ) + difficulty = self.test_data.create_difficulty_level(1) + challenge1 = self.test_data.create_programming_challenge(topic, 1, difficulty) + challenge2 = self.test_data.create_programming_challenge(topic, 2, difficulty) + self.test_data.add_challenge_lesson_relationship( + challenge1, + lesson, + 1, + 1 + ) + self.test_data.add_challenge_lesson_relationship( + challenge2, + lesson, + 1, + 2 + ) + kwargs = { + "topic_slug": topic.slug, + "unit_plan_slug": unit_plan.slug, + "lesson_slug": lesson.slug, + } + url = reverse("topics:programming_challenges_list", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual( + response.context["topic"], + topic + ) + + def test_programming_challenge_list_unit_plan_context(self): + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + age_group_1 = self.test_data.create_age_group(5, 7) + lesson = self.test_data.create_lesson( + topic, + unit_plan, + 1, + age_group_1 + ) + difficulty = self.test_data.create_difficulty_level(1) + challenge1 = self.test_data.create_programming_challenge(topic, 1, difficulty) + challenge2 = self.test_data.create_programming_challenge(topic, 2, difficulty) + self.test_data.add_challenge_lesson_relationship( + challenge1, + lesson, + 1, + 1 + ) + self.test_data.add_challenge_lesson_relationship( + challenge2, + lesson, + 1, + 2 + ) + kwargs = { + "topic_slug": topic.slug, + "unit_plan_slug": unit_plan.slug, + "lesson_slug": lesson.slug, + } + url = reverse("topics:programming_challenges_list", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual( + response.context["unit_plan"], + unit_plan + ) + + def test_programming_challenge_list_lesson_context(self): + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + age_group_1 = self.test_data.create_age_group(5, 7) + lesson = self.test_data.create_lesson( + topic, + unit_plan, + 1, + age_group_1 + ) + difficulty = self.test_data.create_difficulty_level(1) + challenge1 = self.test_data.create_programming_challenge(topic, 1, difficulty) + challenge2 = self.test_data.create_programming_challenge(topic, 2, difficulty) + self.test_data.add_challenge_lesson_relationship( + challenge1, + lesson, + 1, + 1 + ) + self.test_data.add_challenge_lesson_relationship( + challenge2, + lesson, + 1, + 2 + ) + kwargs = { + "topic_slug": topic.slug, + "unit_plan_slug": unit_plan.slug, + "lesson_slug": lesson.slug, + } + url = reverse("topics:programming_challenges_list", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual( + response.context["lesson"], + lesson + ) + + def test_programming_challenge_list_challenges_context(self): + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + age_group_1 = self.test_data.create_age_group(5, 7) + lesson = self.test_data.create_lesson( + topic, + unit_plan, + 1, + age_group_1 + ) + difficulty = self.test_data.create_difficulty_level(1) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + self.test_data.add_challenge_lesson_relationship( + challenge, + lesson, + 1, + 1 + ) + kwargs = { + "topic_slug": topic.slug, + "unit_plan_slug": unit_plan.slug, + "lesson_slug": lesson.slug, + } + url = reverse("topics:programming_challenges_list", kwargs=kwargs) + response = self.client.get(url) + self.assertQuerysetEqual( + response.context["programming_challenges"], + [ + "", + ] + ) diff --git a/csunplugged/tests/topics/views/test_programming_challenge_solution_view.py b/csunplugged/tests/topics/views/test_programming_challenge_solution_view.py new file mode 100644 index 000000000..7dddfe41a --- /dev/null +++ b/csunplugged/tests/topics/views/test_programming_challenge_solution_view.py @@ -0,0 +1,132 @@ +from django.urls import reverse + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + + +class ProgrammingChallengeSolutionViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + self.test_data = TopicsTestDataGenerator() + + def test_programming_challenge_solution_view_with_valid_slugs(self): + topic = self.test_data.create_topic(1) + difficulty = self.test_data.create_difficulty_level(1) + language = self.test_data.create_programming_language(1) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + self.test_data.create_programming_challenge_implementation( + topic, + language, + challenge, + ) + kwargs = { + "topic_slug": topic.slug, + "programming_challenge_slug": challenge.slug, + "programming_language_slug": language.slug, + } + url = reverse("topics:programming_challenge_solution", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + + def test_programming_challenge_solution_view_with_invalid_topic_slug(self): + topic = self.test_data.create_topic(1) + difficulty = self.test_data.create_difficulty_level(1) + language = self.test_data.create_programming_language(1) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + self.test_data.create_programming_challenge_implementation( + topic, + language, + challenge, + ) + kwargs = { + "topic_slug": "wrong_slug", + "programming_challenge_slug": challenge.slug, + "programming_language_slug": language.slug, + } + url = reverse("topics:programming_challenge_solution", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_programming_challenge_solution_view_with_invalid_challenge_slug(self): + topic = self.test_data.create_topic(1) + difficulty = self.test_data.create_difficulty_level(1) + language = self.test_data.create_programming_language(1) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + self.test_data.create_programming_challenge_implementation( + topic, + language, + challenge, + ) + kwargs = { + "topic_slug": topic.slug, + "programming_challenge_slug": "wrong_slug", + "programming_language_slug": language.slug, + } + url = reverse("topics:programming_challenge_solution", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_programming_challenge_solution_view_with_invalid_language_slug(self): + topic = self.test_data.create_topic(1) + difficulty = self.test_data.create_difficulty_level(1) + language = self.test_data.create_programming_language(1) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + self.test_data.create_programming_challenge_implementation( + topic, + language, + challenge, + ) + kwargs = { + "topic_slug": topic.slug, + "programming_challenge_slug": challenge.slug, + "programming_language_slug": "wrong_slug", + } + url = reverse("topics:programming_challenge_solution", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_programming_challenge_solution_view_topic_context(self): + topic = self.test_data.create_topic(1) + difficulty = self.test_data.create_difficulty_level(1) + language = self.test_data.create_programming_language(1) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + self.test_data.create_programming_challenge_implementation( + topic, + language, + challenge, + ) + kwargs = { + "topic_slug": topic.slug, + "programming_challenge_slug": challenge.slug, + "programming_language_slug": language.slug, + } + url = reverse("topics:programming_challenge_solution", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual( + response.context["topic"], + topic + ) + + def test_programming_challenge_solution_view_challenge_context(self): + topic = self.test_data.create_topic(1) + difficulty = self.test_data.create_difficulty_level(1) + language = self.test_data.create_programming_language(1) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + self.test_data.create_programming_challenge_implementation( + topic, + language, + challenge, + ) + kwargs = { + "topic_slug": topic.slug, + "programming_challenge_slug": challenge.slug, + "programming_language_slug": language.slug, + } + url = reverse("topics:programming_challenge_solution", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual( + response.context["programming_challenge"], + challenge + ) diff --git a/csunplugged/tests/topics/views/test_programming_challenge_view.py b/csunplugged/tests/topics/views/test_programming_challenge_view.py new file mode 100644 index 000000000..db45b2f0d --- /dev/null +++ b/csunplugged/tests/topics/views/test_programming_challenge_view.py @@ -0,0 +1,232 @@ +from django.urls import reverse + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + + +class ProgrammingChallengeViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + self.test_data = TopicsTestDataGenerator() + + def test_programming_challenge_view_with_valid_slugs(self): + topic = self.test_data.create_topic(1) + difficulty = self.test_data.create_difficulty_level(1) + language = self.test_data.create_programming_language(1) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + self.test_data.create_programming_challenge_implementation( + topic, + language, + challenge, + ) + kwargs = { + "topic_slug": topic.slug, + "programming_challenge_slug": challenge.slug, + } + url = reverse("topics:programming_challenge", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + + def test_programming_challenge_view_with_invalid_topic_slug(self): + topic = self.test_data.create_topic(1) + difficulty = self.test_data.create_difficulty_level(1) + language = self.test_data.create_programming_language(1) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + self.test_data.create_programming_challenge_implementation( + topic, + language, + challenge, + ) + kwargs = { + "topic_slug": "wrong_slug", + "programming_challenge_slug": challenge.slug, + } + url = reverse("topics:programming_challenge", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_programming_challenge_view_with_invalid_challenge_slug(self): + topic = self.test_data.create_topic(1) + difficulty = self.test_data.create_difficulty_level(1) + language = self.test_data.create_programming_language(1) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + self.test_data.create_programming_challenge_implementation( + topic, + language, + challenge, + ) + kwargs = { + "topic_slug": topic.slug, + "programming_challenge_slug": "wrong_slug", + } + url = reverse("topics:programming_challenge", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_programming_challenge_view_topic_context(self): + topic = self.test_data.create_topic(1) + difficulty = self.test_data.create_difficulty_level(1) + language = self.test_data.create_programming_language(1) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + self.test_data.create_programming_challenge_implementation( + topic, + language, + challenge, + ) + kwargs = { + "topic_slug": topic.slug, + "programming_challenge_slug": challenge.slug, + } + url = reverse("topics:programming_challenge", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual( + response.context["topic"], + topic + ) + + def test_programming_challenge_view_lessons_context(self): + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + age_group_1 = self.test_data.create_age_group(5, 7) + lesson1 = self.test_data.create_lesson( + topic, + unit_plan, + 1, + age_group_1 + ) + lesson2 = self.test_data.create_lesson( + topic, + unit_plan, + 2, + age_group_1 + ) + difficulty = self.test_data.create_difficulty_level(1) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + self.test_data.add_challenge_lesson_relationship( + challenge, + lesson1, + 1, + 1 + ) + self.test_data.add_challenge_lesson_relationship( + challenge, + lesson2, + 1, + 2 + ) + kwargs = { + "topic_slug": topic.slug, + "programming_challenge_slug": challenge.slug, + } + url = reverse("topics:programming_challenge", kwargs=kwargs) + response = self.client.get(url) + self.assertQuerysetEqual( + response.context["lessons"], + [ + "", + "", + ], + ordered=False, + ) + + def test_programming_challenge_view_learning_outcomes_context(self): + topic = self.test_data.create_topic(1) + difficulty = self.test_data.create_difficulty_level(1) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + learning_outcome = self.test_data.create_learning_outcome(1) + challenge.learning_outcomes.add(learning_outcome) + kwargs = { + "topic_slug": topic.slug, + "programming_challenge_slug": challenge.slug, + } + url = reverse("topics:programming_challenge", kwargs=kwargs) + response = self.client.get(url) + self.assertQuerysetEqual( + response.context["learning_outcomes"], + [""] + ) + + def test_programming_challenge_view_learning_outcomes_context_order(self): + topic = self.test_data.create_topic(1) + difficulty = self.test_data.create_difficulty_level(1) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + learning_outcome3 = self.test_data.create_learning_outcome(3) + challenge.learning_outcomes.add(learning_outcome3) + learning_outcome2 = self.test_data.create_learning_outcome(2) + challenge.learning_outcomes.add(learning_outcome2) + learning_outcome1 = self.test_data.create_learning_outcome(1) + challenge.learning_outcomes.add(learning_outcome1) + kwargs = { + "topic_slug": topic.slug, + "programming_challenge_slug": challenge.slug, + } + url = reverse("topics:programming_challenge", kwargs=kwargs) + response = self.client.get(url) + self.assertQuerysetEqual( + response.context["learning_outcomes"], + [ + "", + "", + "", + ] + ) + + def test_programming_challenge_view_implementations_context(self): + topic = self.test_data.create_topic(1) + difficulty = self.test_data.create_difficulty_level(1) + language = self.test_data.create_programming_language(1) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + self.test_data.create_programming_challenge_implementation( + topic, + language, + challenge, + ) + kwargs = { + "topic_slug": topic.slug, + "programming_challenge_slug": challenge.slug, + } + url = reverse("topics:programming_challenge", kwargs=kwargs) + response = self.client.get(url) + self.assertQuerysetEqual( + response.context["implementations"], + [""] + ) + + def test_programming_challenge_view_implementations_context_order(self): + topic = self.test_data.create_topic(1) + difficulty = self.test_data.create_difficulty_level(1) + language1 = self.test_data.create_programming_language(1) + language2 = self.test_data.create_programming_language(2) + language3 = self.test_data.create_programming_language(3) + challenge = self.test_data.create_programming_challenge(topic, 1, difficulty) + self.test_data.create_programming_challenge_implementation( + topic, + language3, + challenge, + ) + self.test_data.create_programming_challenge_implementation( + topic, + language2, + challenge, + ) + self.test_data.create_programming_challenge_implementation( + topic, + language1, + challenge, + ) + kwargs = { + "topic_slug": topic.slug, + "programming_challenge_slug": challenge.slug, + } + url = reverse("topics:programming_challenge", kwargs=kwargs) + response = self.client.get(url) + self.assertQuerysetEqual( + response.context["implementations"], + [ + "", + "", + "", + ] + ) diff --git a/csunplugged/tests/topics/views/test_topic_view.py b/csunplugged/tests/topics/views/test_topic_view.py new file mode 100644 index 000000000..3fbf966f0 --- /dev/null +++ b/csunplugged/tests/topics/views/test_topic_view.py @@ -0,0 +1,140 @@ +from django.urls import reverse + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator +from tests.resources.ResourcesTestDataGenerator import ResourcesTestDataGenerator +from topics.models import ResourceDescription + + +class TopicViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + self.test_data = TopicsTestDataGenerator() + + def test_topic_view_with_valid_slug(self): + topic = self.test_data.create_topic(1) + kwargs = { + "topic_slug": topic.slug + } + url = reverse("topics:topic", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + + def test_topic_view_with_invalid_slug(self): + self.test_data.create_topic(1) + kwargs = { + "topic_slug": "wrong_slug", + } + url = reverse("topics:topic", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_topic_view_topic_context(self): + topic = self.test_data.create_topic(1) + kwargs = { + "topic_slug": topic.slug, + } + url = reverse("topics:topic", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual( + response.context["topic"], + topic + ) + + def test_topic_view_lessons_context(self): + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + age_group_1 = self.test_data.create_age_group(5, 7) + lesson1 = self.test_data.create_lesson( + topic, + unit_plan, + 1, + age_group_1 + ) + lesson2 = self.test_data.create_lesson( + topic, + unit_plan, + 2, + age_group_1 + ) + kwargs = { + "topic_slug": topic.slug, + } + url = reverse("topics:topic", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual( + len(response.context["grouped_lessons"]), + 1 + ) + grouped_lessons = response.context["grouped_lessons"] + for (age_group, lessons) in grouped_lessons.items(): + self.assertEqual(repr(age_group), "") + self.assertEqual( + lessons, + [lesson1, lesson2] + ) + + def test_topic_view_resources_context(self): + resource_test_data = ResourcesTestDataGenerator() + # Create topic data + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + age_group_1 = self.test_data.create_age_group(5, 7) + lesson1 = self.test_data.create_lesson( + topic, + unit_plan, + 1, + age_group_1 + ) + lesson2 = self.test_data.create_lesson( + topic, + unit_plan, + 2, + age_group_1 + ) + # Create resource data + resource1 = resource_test_data.create_resource( + "binary-cards", + "Binary Cards (small)", + "resources/binary-cards-small.html", + "binary_cards_small.py", + ) + resource2 = resource_test_data.create_resource( + "binary-windows", + "Binary Windows", + "resources/binary-windows.html", + "binary_windows.py", + ) + # Create relationships + relationship1 = ResourceDescription( + resource=resource1, + lesson=lesson1, + description="Description of Binary Cards (small)" + ) + relationship1.save() + relationship2 = ResourceDescription( + resource=resource2, + lesson=lesson2, + description="Description of Binary Windows" + ) + relationship2.save() + # Perform tests + kwargs = { + "topic_slug": topic.slug, + } + url = reverse("topics:topic", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual( + len(response.context["resources"]), + 2 + ) + self.assertQuerysetEqual( + response.context["resources"], + [ + "", + "", + ], + ordered=False, + ) diff --git a/csunplugged/tests/topics/views/test_unit_plan_view.py b/csunplugged/tests/topics/views/test_unit_plan_view.py new file mode 100644 index 000000000..30a668c3b --- /dev/null +++ b/csunplugged/tests/topics/views/test_unit_plan_view.py @@ -0,0 +1,156 @@ +from django.urls import reverse + +from tests.BaseTestWithDB import BaseTestWithDB +from tests.topics.TopicsTestDataGenerator import TopicsTestDataGenerator + + +class UnitPlanViewTest(BaseTestWithDB): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + self.test_data = TopicsTestDataGenerator() + + def test_unit_plan_view_with_valid_slugs(self): + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + kwargs = { + "topic_slug": topic.slug, + "unit_plan_slug": unit_plan.slug, + } + url = reverse("topics:unit_plan", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(200, response.status_code) + + def test_unit_plan_view_with_invalid_topic_slug(self): + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + kwargs = { + "topic_slug": "wrong_slug", + "unit_plan_slug": unit_plan.slug, + } + url = reverse("topics:unit_plan", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_unit_plan_view_with_invalid_unit_plan_slug(self): + topic = self.test_data.create_topic(1) + self.test_data.create_unit_plan(topic, 1) + kwargs = { + "topic_slug": topic.slug, + "unit_plan_slug": "wrong_slug", + } + url = reverse("topics:unit_plan", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(404, response.status_code) + + def test_unit_plan_view_topic_context(self): + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + kwargs = { + "topic_slug": topic.slug, + "unit_plan_slug": unit_plan.slug, + } + url = reverse("topics:unit_plan", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual( + response.context["topic"], + topic + ) + + def test_unit_plan_view_lessons_context(self): + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + age_group_1 = self.test_data.create_age_group(5, 7) + lesson1 = self.test_data.create_lesson( + topic, + unit_plan, + 1, + age_group_1 + ) + lesson2 = self.test_data.create_lesson( + topic, + unit_plan, + 2, + age_group_1 + ) + kwargs = { + "topic_slug": topic.slug, + "unit_plan_slug": unit_plan.slug, + } + url = reverse("topics:unit_plan", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual( + len(response.context["grouped_lessons"]), + 1 + ) + grouped_lessons = response.context["grouped_lessons"] + for (age_group, lessons) in grouped_lessons.items(): + self.assertEqual(repr(age_group), "") + self.assertEqual( + lessons, + [lesson1, lesson2] + ) + + def test_unit_plan_view_lessons_context_order(self): + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + age_group_1 = self.test_data.create_age_group(5, 7) + age_group_2 = self.test_data.create_age_group(8, 10) + lesson1 = self.test_data.create_lesson( + topic, + unit_plan, + 1, + age_group_2 + ) + lesson2 = self.test_data.create_lesson( + topic, + unit_plan, + 2, + age_group_1 + ) + lesson3 = self.test_data.create_lesson( + topic, + unit_plan, + 1, + age_group_1 + ) + kwargs = { + "topic_slug": topic.slug, + "unit_plan_slug": unit_plan.slug, + } + url = reverse("topics:unit_plan", kwargs=kwargs) + response = self.client.get(url) + self.assertEqual( + len(response.context["grouped_lessons"]), + 2 + ) + + expected_grouped_lessons = [ + ("", [lesson3, lesson2]), + ("", [lesson1]), + ] + grouped_lessons = response.context["grouped_lessons"] + i = 0 + for (age_group, lessons) in grouped_lessons.items(): + self.assertEqual(repr(age_group), expected_grouped_lessons[i][0]) + self.assertEqual( + lessons, + expected_grouped_lessons[i][1] + ) + i += 1 + + def test_unit_plan_view_templates(self): + topic = self.test_data.create_topic(1) + unit_plan = self.test_data.create_unit_plan(topic, 1) + kwargs = { + "topic_slug": topic.slug, + "unit_plan_slug": unit_plan.slug, + } + url = reverse("topics:unit_plan", kwargs=kwargs) + response = self.client.get(url) + template_found = False + for template in response.templates: + if template.name == "topics/unit-plan.html": + template_found = True + self.assertTrue(template_found) diff --git a/csunplugged/tests/utils/__init__.py b/csunplugged/tests/utils/__init__.py new file mode 100644 index 000000000..9ec471d6a --- /dev/null +++ b/csunplugged/tests/utils/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the utils files.""" diff --git a/csunplugged/tests/utils/errors/__init__.py b/csunplugged/tests/utils/errors/__init__.py new file mode 100644 index 000000000..5f6c9d36f --- /dev/null +++ b/csunplugged/tests/utils/errors/__init__.py @@ -0,0 +1 @@ +"""Module for tests of custom errors.""" diff --git a/csunplugged/tests/utils/errors/test_InvalidConfigValueError.py b/csunplugged/tests/utils/errors/test_InvalidConfigValueError.py new file mode 100644 index 000000000..5e1430c7f --- /dev/null +++ b/csunplugged/tests/utils/errors/test_InvalidConfigValueError.py @@ -0,0 +1,37 @@ +"""Test class for InvalidConfigValueError error.""" + +from django.test import SimpleTestCase +from utils.errors.InvalidConfigValueError import InvalidConfigValueError + + +class InvalidConfigValueErrorTest(SimpleTestCase): + """Test class for InvalidConfigValueError error. + + Note: Tests to check if these were raised appropriately + are located where this exception is used (for + example: topic loaders). + """ + + def test_invalid_config_value_error_attributes(self): + exception = InvalidConfigValueError( + "yaml file path", + "key", + "expected" + ) + self.assertEqual(exception.yaml_file_path, "yaml file path") + self.assertEqual(exception.key, "key") + self.assertEqual(exception.expected, "expected") + + def test_invalid_config_value_error_string(self): + exception = InvalidConfigValueError( + "yaml file path", + "key", + "expected" + ) + expected_string = ( + "\n****************************ERROR****************************\n" + "File: yaml file path\n\n" + "Invalid configuration file value for: key\n\n" + "Expected: expected\n" + ) + self.assertEqual(exception.__str__(), expected_string) diff --git a/csunplugged/topics/__init__.py b/csunplugged/topics/__init__.py new file mode 100644 index 000000000..bc3390ce4 --- /dev/null +++ b/csunplugged/topics/__init__.py @@ -0,0 +1 @@ +"""Module for the topics application.""" diff --git a/csunplugged/topics/apps.py b/csunplugged/topics/apps.py new file mode 100644 index 000000000..039913f63 --- /dev/null +++ b/csunplugged/topics/apps.py @@ -0,0 +1,9 @@ +"""Application configuration for the topics application.""" + +from django.apps import AppConfig + + +class TopicsConfig(AppConfig): + """Configuration object for the topics application.""" + + name = "topics" diff --git a/csunplugged/topics/content/en/age-groups.yaml b/csunplugged/topics/content/en/age-groups.yaml new file mode 100644 index 000000000..f4708d201 --- /dev/null +++ b/csunplugged/topics/content/en/age-groups.yaml @@ -0,0 +1,10 @@ +5-7: + min_age: 5 + max_age: 7 + description: In the teacher observations sections there may also be background notes on the big picture. There is no expectation that 5 to 7 year olds will need to know this, but if you are asked, you have the answer at your fingertips. +8-10: + min_age: 8 + max_age: 10 +11-14: + min_age: 11 + max_age: 14 diff --git a/csunplugged/topics/content/en/binary-numbers/binary-numbers.md b/csunplugged/topics/content/en/binary-numbers/binary-numbers.md new file mode 100644 index 000000000..f101969c0 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/binary-numbers.md @@ -0,0 +1,6 @@ +# Binary numbers + +Computers today use digits to represent information - that's why they're called digital systems. +The simplest and most common way to represent digits is the binary number system, with just two digits (usually written as 0 and 1). +It is called binary because there are only two different digits used, or two states. +This unit and lesson explores how the binary system works and why it’s important to understand how data is represented. diff --git a/csunplugged/topics/content/en/binary-numbers/binary-numbers.yaml b/csunplugged/topics/content/en/binary-numbers/binary-numbers.yaml new file mode 100644 index 000000000..5cf1f790e --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/binary-numbers.yaml @@ -0,0 +1,9 @@ +unit-plans: + - unit-plan/unit-plan.yaml + +icon: img/topics/binary-cards-4-bits-icon.png + +other-resources: other-resources.md + +programming-challenges: programming-challenges/programming-challenges.yaml +curriculum-integrations: curriculum-integrations/curriculum-integrations.yaml diff --git a/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/binary-art.md b/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/binary-art.md new file mode 100644 index 000000000..0e198b029 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/binary-art.md @@ -0,0 +1,7 @@ +# Binary Art + +{image file-path="img/topics/binary_grids.png"} + +- Write a secret message by colouring in the grids +- Write your name in binary art +- Write your birthdate in binary art \ No newline at end of file diff --git a/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/binary-name-necklaces.md b/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/binary-name-necklaces.md new file mode 100644 index 000000000..656c8997f --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/binary-name-necklaces.md @@ -0,0 +1,9 @@ +# Binary Name Necklaces + +{image file-path="img/topics/col_binary_necklace_copy.png"} + +Create a necklace or a bag tag with your initials written in 5 bit binary. + +Decide on which bead colour will be a 1 and which bead will be a 0. +Computers don’t need to know when a new letter starts because it knows the rule that every 5th bit is a new letter. +The lowest value bit of each group of 5 goes on the right. diff --git a/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/binary-or-normal-candles.md b/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/binary-or-normal-candles.md new file mode 100644 index 000000000..85e1620ef --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/binary-or-normal-candles.md @@ -0,0 +1,16 @@ +# Binary Candles or Normal Candles on your Cake + +On a birthday cake we often use one candle for each year of age. + +But since each candle can be either lit or not lit, we could use them to show a binary representation of your age. +For example, 14 years old is 1110 in binary, so you could represent it with four candles. + +{image file-path="img/topics/col_binary_cake.png"} + +Persuade people to start using binary candles on their birthday cake. + +- What are the advantages of using binary candles? +- Why do binary candles get better when you get older? +- Are there any disadvantages of using binary candles and how would you overcome them? + +(By the way, the traditional system is called unary, or base one. Each candle is worth one times as much as the previous one!) diff --git a/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/binary-patterns.md b/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/binary-patterns.md new file mode 100644 index 000000000..5eac99463 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/binary-patterns.md @@ -0,0 +1,7 @@ +# Binary Patterns + +- Use a grid of 5 x 31 squares +- Colour the first row 00001 +- Colour the second row 00010 +- Continue colouring the sequence of binary numbers until you can represent through to 31 +- This sample shows the pattern through to 16. diff --git a/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/binary-tunes.md b/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/binary-tunes.md new file mode 100644 index 000000000..77f11d7f7 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/binary-tunes.md @@ -0,0 +1,11 @@ +# Binary Tunes + +- Use a high note for a 1, and a low note for a 0 +- Represent letters using the binary numbers from 00001 to 11010 (1 to 26) +- This is explained in more detail here, with examples: [http://csunplugged.org/modems-unplugged-2/](http://csunplugged.org/modems-unplugged-2/) +- Steganography: is a message hidden in something else - in this case, a song +- Write your own song that contains a hidden message. + +#### Extension + +Consider hiding the binary numbers in the rhythm (long and short notes), or make a video with dance moves that code binary numbers diff --git a/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/biographies-and-binary-number-system-history.md b/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/biographies-and-binary-number-system-history.md new file mode 100644 index 000000000..772d2e200 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/biographies-and-binary-number-system-history.md @@ -0,0 +1,27 @@ +# Biographies and binary number system history + +Imagine you have been asked to introduce one of the following people, write an introduction that explains why they are famous and some interesting information about them. + +Or + +Write a biography of one of the following people: what situation were they living in, what did they achieve, and what impact has that had on society? + +A biography: + +- Gives an account of a person’s life +- Start by introducing the person and what they achieved +- Write about the significant events that happened in their lives +- Write about the impact their work has had on other people + +Computer science started hundreds of years ago when people started thinking about how to represent and manipulate data. +Some of the people who worked on early ideas have modern concepts named after them (such as Boolean true/false values, named after George Boole). +Find out more about the people whose work is used every day in computers today. +We've given some links to Wikipedia below to get you started, but make sure you look more widely than that. + +Some people and historic ideas to research about are: + +- George Boole did extensive work on the idea of representing things with just two values, and many current programming languages have "Boolean variables", which are named after him. [https://en.wikipedia.org/wiki/George_Boole](https://en.wikipedia.org/wiki/George_Boole) +- Baudot, telegraph codes, Braille, Morse all relate to binary representations (although Morse really uses three symbols: dot, dash and gap). + See what you can find out about the lives of Baudot, Braille or Morse, and what led them to invent their codes. +- Donald Murray (who was born in NZ) came up with a letter code that was widely used for some time [https://en.wikipedia.org/wiki/Donald_Murray_(inventor)](https://en.wikipedia.org/wiki/Donald_Murray_(inventor)) +- Early telegraph systems sometimes used binary representations; you could research shutter telegraphs: [https://en.wikipedia.org/wiki/Semaphore_line](https://en.wikipedia.org/wiki/Semaphore_line) diff --git a/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/curriculum-integrations.yaml b/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/curriculum-integrations.yaml new file mode 100644 index 000000000..cce597518 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/curriculum-integrations.yaml @@ -0,0 +1,50 @@ +binary-or-normal-candles: + number: 1 + curriculum-areas: + - writing + prerequisite-lessons: + unit-plan: + - how-binary-digits-work + +whose-cake-is-it: + number: 2 + curriculum-areas: + - writing + prerequisite-lessons: + unit-plan: + - how-binary-digits-work + +binary-name-necklaces: + number: 3 + curriculum-areas: + - art + prerequisite-lessons: + unit-plan: + - codes-for-letters-using-binary-representation + +binary-patterns: + number: 4 + curriculum-areas: + - art + prerequisite-lessons: + unit-plan: + - how-binary-digits-work + +binary-tunes: + number: 5 + curriculum-areas: + - music + prerequisite-lessons: + unit-plan: + - how-binary-digits-work + +biographies-and-binary-number-system-history: + number: 6 + curriculum-areas: + - reading + - writing + +binary-art: + number: 7 + curriculum-areas: + - art diff --git a/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/whose-cake-is-it.md b/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/whose-cake-is-it.md new file mode 100644 index 000000000..7e8fd0bc1 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/curriculum-integrations/whose-cake-is-it.md @@ -0,0 +1,7 @@ +#Whose cake is it? + +Write an explanation as to why there is confusion as to whose cake it is? +Remember to add a conclusion as to who you think should have the cake and why? +There is more than one possible explanation. + +{image file-path="img/topics/col_binary_birthdayFinal.png"} diff --git a/csunplugged/topics/content/en/binary-numbers/other-resources.md b/csunplugged/topics/content/en/binary-numbers/other-resources.md new file mode 100644 index 000000000..5c13d1218 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/other-resources.md @@ -0,0 +1,5 @@ +# Other Resources + + +The [Computer Science Field Guide](http://csfieldguide.org.nz/) has a chapter on [Data Representation](http://csfieldguide.org.nz/en/chapters/data-representation.html) which covers Binary Numbers and how they can represent different different types of data. +It also has an interactive version of the [binary cards](http://csfieldguide.org.nz/en/interactives/binary-cards/index.html?digits=4&start=WWWW), you can find out extra information on how to use these [here](https://github.com/uccser/cs-field-guide/tree/develop/interactives/binary-cards). \ No newline at end of file diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-as-dots/binary-cards-as-dots.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-as-dots/binary-cards-as-dots.md new file mode 100644 index 000000000..161482602 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-as-dots/binary-cards-as-dots.md @@ -0,0 +1,14 @@ +# Display binary cards with dots representing a number between 0 and 31 + +## Requirement: + +Write a program that asks the user to enter a decimal number between 0 and 31 as the input and displays the binary cards (5 black or white cards) representing the number in binary as the output. + +## Testing examples: + +Your program should display the outputs shown in this table for the given inputs provided; + +| Input | Output | +| ----- | ------------------------------------------------------------------------------------------- | +| 31 | {image-inline file-path="img/topics/programming-challenges/binary-challenge-9-example1.png"} | +| 11 | {image-inline file-path="img/topics/programming-challenges/binary-challenge-9-example2.png"} | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-as-dots/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-as-dots/scratch-expected.md new file mode 100644 index 000000000..a05091ce7 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-as-dots/scratch-expected.md @@ -0,0 +1,3 @@ +Click on the green flag to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/158861081/?autostart=false"} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-as-dots/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-as-dots/scratch-hints.md new file mode 100644 index 000000000..b1a058e17 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-as-dots/scratch-hints.md @@ -0,0 +1,19 @@ +We have provided 4 different solutions to code this program, based on two template programs. + +The first three sample solutions are based on [remixing this program](https://scratch.mit.edu/projects/159437081/). +Remix this program and change its name to: Binary challenge 9 (version 1). + +For this version, all cards should change to black when the green flag is pressed, +and then a program can work through each of the bit values and broadcast to a +card if it should change costume. + +The fourth variation is based on remixing [this program](https://scratch.mit.edu/projects/159499667/). +Remix this program and change its name to: Binary challenge 9 (version 2). + +This solution displays 5 cards (with dots showing for all the cards) on the +screen and asks the user to enter a number between 0 and 31. +It then clones a black card sprite over the cards which need to be hidden (dots not showing). + +The fourth variation should work like this: + +{iframe link="https://scratch.mit.edu/projects/embed/159501752/?autostart=false"} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-as-dots/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-as-dots/scratch-solution.md new file mode 100644 index 000000000..7055e7349 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-as-dots/scratch-solution.md @@ -0,0 +1,249 @@ +{panel type="general" title="Solution 1" subtitle="(check digits of the binary number)"} + +**Blocks for cat sprite (labelled binary number)** + +```scratch +when green flag clicked +ask [Please enter a decimal number between 0 and 31] and wait +set [decimal number v] to (answer) +set [bit value v] to [32] +set [binary number v] to [] +repeat until <(bit value) = [1]> + set [bit value v] to ((bit value) / (2)) + if <<(decimal number) > (bit value)> or <(decimal number) = (bit value)>> then + set [binary number v] to (join (binary number) [1]) + set [decimal number v] to ((decimal number) - (bit value)) + else + set [binary number v] to (join (binary number) [0]) + end +end +broadcast [turn over card v] +``` + +**Blocks for 1 dot card sprite** + +```scratch:inline +when green flag clicked +switch costume to [black v] + +when I receive [turn over card v] +if <(letter (5) of (binary number)) = [1]> then + switch costume to [white v] +end +``` + +**Blocks for 2 dots card sprite** + +```scratch:inline +when green flag clicked +switch costume to [black v] + +when I receive [turn over card v] +if <(letter (4) of (binary number)) = [1]> then + switch costume to [white v] +end +``` + +**Blocks for 4 dots card sprite** + +```scratch:inline +when green flag clicked +switch costume to [black v] + +when I receive [turn over card v] +if <(letter (3) of (binary number)) = [1]> then + switch costume to [white v] +end +``` + +**Blocks for 8 dots card sprite** + +```scratch:inline +when green flag clicked +switch costume to [black v] + +when I receive [turn over card v] +if <(letter (2) of (binary number)) = [1]> then + switch costume to [white v] +end +``` + +**Blocks for 16 dots card sprite** + +```scratch:inline +when green flag clicked +switch costume to [black v] + +when I receive [turn over card v] +if <(letter (1) of (binary number)) = [1]> then + switch costume to [white v] +end +``` + +{panel end} + +{panel type="general" title="Solution 2" subtitle="(broadcast the bit value)"} + +**Blocks for cat sprite (labelled binary number)** + +```scratch +when green flag clicked +ask [Please enter a decimal number between 0 and 31] and wait +set [decimal number v] to (answer) +set [bit value v] to [32] +repeat until <(bit value) = [1]> + set [bit value v] to ((bit value) / (2)) + if <<(decimal number) > (bit value)> or <(decimal number) = (bit value)>> then + set [decimal number v] to ((decimal number) - (bit value)) + broadcast (bit value) + end +end +``` + +**Blocks for 1 dot card sprite** + +```scratch:inline +when green flag clicked +switch costume to [black v] + +when I receive [1 v] +switch costume to [white v] +``` + +**Blocks for 2 dots card sprite** + +```scratch:inline +when green flag clicked +switch costume to [black v] + +when I receive [2 v] +switch costume to [white v] +``` + +**Blocks for 4 dots card sprite** + +```scratch:inline +when green flag clicked +switch costume to [black v] + +when I receive [4 v] +switch costume to [white v] +``` + +**Blocks for 8 dots card sprite** + +```scratch:inline +when green flag clicked +switch costume to [black v] + +when I receive [8 v] +switch costume to [white v] +``` + +**Blocks for 16 dots card sprite** + +```scratch:inline +when green flag clicked +switch costume to [black v] + +when I receive [16 v] +switch costume to [white v] +``` + +{panel end} + +{panel type="general" title="Solution 3" subtitle="(using a variable to broadcast the bit value)"} + +**Blocks for cat sprite (labelled binary number)** + +```scratch +when green flag clicked +ask [Please enter a decimal number between 0 and 31] and wait +set [decimal number v] to (answer) +set [bit value v] to [32] +repeat until <(bit value) = [1]> + set [bit value v] to ((bit value) / (2)) + if <<(decimal number) > (bit value)> or <(decimal number) = (bit value)>> then + set [decimal number v] to ((decimal number) - (bit value)) + set [number of dots v] to (bit value) + end +end +``` + +**Blocks for 1 dot card sprite** + +```scratch:inline +when green flag clicked +set [number of dots v] to [0] +switch costume to [black v] +wait until <(number of dots) = [1]> +switch costume to [white v] +``` + +**Blocks for 2 dots card sprite** + +```scratch:inline +when green flag clicked +set [number of dots v] to [0] +switch costume to [black v] +wait until <(number of dots) = [2]> +switch costume to [white v] +``` + +**Blocks for 4 dots card sprite** + +```scratch:inline +when green flag clicked +set [number of dots v] to [0] +switch costume to [black v] +wait until <(number of dots) = [4]> +switch costume to [white v] +``` + +**Blocks for 8 dots card sprite** + +```scratch:inline +when green flag clicked +set [number of dots v] to [0] +switch costume to [black v] +wait until <(number of dots) = [8]> +switch costume to [white v] +``` + +**Blocks for 16 dots card sprite** + +```scratch:inline +when green flag clicked +set [number of dots v] to [0] +switch costume to [black v] +wait until <(number of dots) = [16]> +switch costume to [white v] +``` + +{panel end} + +{panel type="general" title="Solution 4" subtitle="(cloning the black card)"} + +**Blocks for black card sprite** + +```scratch +when green flag clicked +hide +set [x v] to [-190] +ask [Please enter a decimal number between 0 and 31] and wait +set [decimal number v] to (answer) +set [bit value v] to [32] +repeat until <(bit value) = [1]> + set [bit value v] to ((bit value) / (2)) + if <<(decimal number) > (bit value)> or <(decimal number) = (bit value)>> then + set [decimal number v] to ((decimal number) - (bit value)) + else + go to x: (x) y: (0) + show + create clone of [myself v] + end + change [x v] by (94) +end +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-any-number/binary-cards-representing-any-number.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-any-number/binary-cards-representing-any-number.md new file mode 100644 index 000000000..8e88d0400 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-any-number/binary-cards-representing-any-number.md @@ -0,0 +1,19 @@ +# Display the binary cards needed to represent any decimal number + +## Requirement: + +Write a program that asks the user to enter any decimal number as the input +and displays the binary cards representing that number (B for displaying +black cards with no dots, and W for displaying white cards with dots) as the +output. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +|-------|-----------------------------------------------------------| +| 11 | The binary representation for the number 11 is WBWW | +| 255 | The binary representation for the number 255 is WWWWWWWW | +| 256 | The binary representation for the number 256 is WBBBBBBBB | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-any-number/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-any-number/scratch-expected.md new file mode 100644 index 000000000..9bf4259fe --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-any-number/scratch-expected.md @@ -0,0 +1,45 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148424247/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Please enter a decimal number:] and wait + +say (join (join (join [The binary representation for the number ] (answer)) [ is ]) (cards)) +``` + +```scratch:split:random +repeat until <(number) < (bit value)> +end + +if <<(number) > (bit value)> or <(number) = (bit value)>> then +else +end + +repeat until <(bit value) = [1]> +end +``` + +```scratch:split:random +set [number v] to (answer) + +set [bit value v] to [1] + +set [cards v] to [] + +set [bit value v] to ((bit value) * (2)) + +set [bit value v] to ((bit value) / (2)) + +set [cards v] to (join (cards) [W]) + +set [number v] to ((number) - (bit value)) + +set [cards v] to (join (cards) [B]) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-any-number/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-any-number/scratch-hints.md new file mode 100644 index 000000000..49b2f27da --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-any-number/scratch-hints.md @@ -0,0 +1,25 @@ +- Make variables called: + + - “number” and set its value to the input number given by the end user. + - “bit value” and set its value to ‘1’. + Find the smallest bit value which is larger than the “number” by + doubling value of “bit value” until it is bigger than the “number”. + - “cards” is a string variable and stores the binary cards needed (‘B’ + for black cards and ‘W’ for white cards). + +- Set the variable “bit value” to 1 and find the smallest “bit value” which + is larger than “number” by multiplying “bit value” by 2 (Use the + `scratch:() * ()` block under “Operators” to multiply the “bit value” by + 2) until it is larger than “number”. + You can do this by using a `scratch:repeat until <>` loop. + +- Now divide the “bit value” by 2 and check if “number” is greater than or + equal to “bit value”. + If it is, add ‘W’ to string variable “cards” and subtract “bit value” + from the “number”. + If not, add ‘B’ to string variable “cards”. + Repeat until “bit value” is equal to 1. + Display the value of “cards” as the output. + +- Test your program with some values on the boundaries (for example test it + with numbers 255 and 256). diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-any-number/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-any-number/scratch-solution.md new file mode 100644 index 000000000..f19150158 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-any-number/scratch-solution.md @@ -0,0 +1,20 @@ +```scratch +when green flag clicked +ask [Please enter a decimal number:] and wait +set [number v] to (answer) +set [bit value v] to [1] +set [cards v] to [] +repeat until <(number) < (bit value)> + set [bit value v] to ((bit value) * (2)) +end +repeat until <(bit value) = [1]> + set [bit value v] to ((bit value) / (2)) + if <<(number) > (bit value)> or <(number) = (bit value)>> then + set [cards v] to (join (cards) [W]) + set [number v] to ((number) - (bit value)) + else + set [cards v] to (join (cards) [B]) + end +end +say (join (join (join [The binary representation for the number ] (answer)) [ is ]) (cards)) +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-number/binary-cards-representing-number.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-number/binary-cards-representing-number.md new file mode 100644 index 000000000..b7c843089 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-number/binary-cards-representing-number.md @@ -0,0 +1,19 @@ +# Display the binary cards needed to represent a decimal number between 0 and 31 + +## Requirement: + +Write a program that asks the user to enter a decimal number between 0 and 31 +as the input and displays the 5 binary cards representing that number (B for +displaying black cards with no dots, and W for displaying white cards with +dots) as the output. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +|-------|------------------------------------------------------| +| 11 | The binary representation for the number 11 is BWBWW | +| 31 | The binary representation for the number 31 is WWWWW | +| 0 | The binary representation for the number 0 is BBBBB | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-number/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-number/scratch-expected.md new file mode 100644 index 000000000..40065e06d --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-number/scratch-expected.md @@ -0,0 +1,38 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/159486263/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Please enter a number between 0 and 31:] and wait + +if <<(number) > (bit value)> or <(number) = (bit value)>> then +else +end + +repeat until <(bit value) = [1]> +end + +say (join (join (join [The binary representation for the number ] (answer)) [ is ]) (cards)) +``` + +```scratch:split:random +set [number v] to (answer) + +set [bit value v] to [32] + +set [cards v] to [] + +set [bit value v] to ((bit value) / (2)) + +set [cards v] to (join (cards) [W]) + +set [number v] to ((number) - (bit value)) + +set [cards v] to (join (cards) [B]) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-number/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-number/scratch-hints.md new file mode 100644 index 000000000..a85558306 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-number/scratch-hints.md @@ -0,0 +1,17 @@ +- Make variables called: + + - “number” and set its value to the input number given by the end user. + - “bit value” and set its value to ‘32’. + - “cards” is a string variable and stores the binary cards needed (‘B’ + for black cards and ‘W’ for white cards). + +- Now divide the “bit value” by 2 and check if “number” is greater than or + equal to “bit value”. + If it is, add ‘W’ to string variable “cards” and subtract “bit value” + from the “number”. + If not, add ‘B’ to string variable “cards”. + Repeat until “bit value” is equal to 1. + Display the value of “cards” as the output. + +- Test your program with some values on the boundaries (for example test it + with numbers 0 and 31). diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-number/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-number/scratch-solution.md new file mode 100644 index 000000000..63dd1bf8c --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-cards-representing-number/scratch-solution.md @@ -0,0 +1,17 @@ +```scratch +when green flag clicked +ask [Please enter a number between 0 and 31:] and wait +set [number v] to (answer) +set [bit value v] to [32] +set [cards v] to [] +repeat until <(bit value) = [1]> + set [bit value v] to ((bit value) / (2)) + if <<(number) > (bit value)> or <(number) = (bit value)>> then + set [cards v] to (join (cards) [W]) + set [number v] to ((number) - (bit value)) + else + set [cards v] to (join (cards) [B]) + end +end +say (join (join (join [The binary representation for the number ] (answer)) [ is ]) (cards)) +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-any-number/binary-notes-representing-any-number.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-any-number/binary-notes-representing-any-number.md new file mode 100644 index 000000000..ebbd422cf --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-any-number/binary-notes-representing-any-number.md @@ -0,0 +1,18 @@ +# Binary representation of any decimal number by playing musical notes + +## Requirement: + +Write a program that asks the user to enter any decimal number as the input +and plays the musical notes representing that number in binary (high notes for +dots showing and low notes for dots not showing). + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +|-------|------------------------------------------------| +| 255 | high, high, high, high, high, high, high, high | +| 100 | high, high, low, low, high, low, low | +| 1 | high | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-any-number/extra-challenge.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-any-number/extra-challenge.md new file mode 100644 index 000000000..4f45ba0b4 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-any-number/extra-challenge.md @@ -0,0 +1,2 @@ +Modify your program so it would play a low note when the user enters the number +zero as the input. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-any-number/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-any-number/scratch-expected.md new file mode 100644 index 000000000..46935e6a4 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-any-number/scratch-expected.md @@ -0,0 +1,43 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/158861213/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Please enter a decimal number:] and wait +``` + +```scratch:split:random +play note (72 v) for (0.5) beats + +play note (48 v) for (0.5) beats +``` + +```scratch:split:random +repeat until <(decimal number) < (bit value)> +end + +repeat until <(bit value) = [1]> +end + +if <<(decimal number) > (bit value)> or <(decimal number) = (bit value)>> then +else +end +``` + +```scratch:split:random +set [decimal number v] to (answer) + +set [bit value v] to [1] + +set [bit value v] to ((bit value) * (2)) + +set [bit value v] to ((bit value) / (2)) + +set [decimal number v] to ((decimal number) - (bit value)) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-any-number/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-any-number/scratch-hints.md new file mode 100644 index 000000000..26f271593 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-any-number/scratch-hints.md @@ -0,0 +1,22 @@ +- Make variables called: + + - “decimal number” and set its value to the input number given by the + end user. + - “bit value” and set its value to ‘1’. + Find the smallest bit value which is larger than the “decimal number” + by doubling value of “bit value” until it is bigger than the “decimal + number”. + +- Set the variable “bit value” to 1 and find the smallest “bit value” which + is larger than “decimal number” by multiplying “bit value” by 2 + (Use the `scratch:() * ()` block under “Operators” to multiply the “bit + value” by 2) until it is larger than “decimal number”. + You can do this by using a `scratch:repeat until <>` loop. +- Now divide the “bit value” by 2 and check if “decimal number” is greater + than or equal to “bit value”. + If it is, play a high note and subtract “bit value” from the “decimal number”. + If not, play a low note. + Repeat until “bit value” is equal to 1. +- To play a musical note use the block + `scratch:play note (60 v) for (0.5) beats` beats under the “Sound”. + Type in the number “72” for a high note and “48” for a low note. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-any-number/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-any-number/scratch-solution.md new file mode 100644 index 000000000..eba290d09 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-any-number/scratch-solution.md @@ -0,0 +1,18 @@ +```scratch +when green flag clicked +ask [Please enter a decimal number:] and wait +set [decimal number v] to (answer) +set [bit value v] to [1] +repeat until <(decimal number) < (bit value)> + set [bit value v] to ((bit value) * (2)) +end +repeat until <(bit value) = [1]> + set [bit value v] to ((bit value) / (2)) + if <<(decimal number) > (bit value)> or <(decimal number) = (bit value)>> then + play note (72 v) for (0.5) beats + set [decimal number v] to ((decimal number) - (bit value)) + else + play note (48 v) for (0.5) beats + end +end +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-number/binary-notes-representing-number.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-number/binary-notes-representing-number.md new file mode 100644 index 000000000..7ddd2431d --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-number/binary-notes-representing-number.md @@ -0,0 +1,18 @@ +# Binary representation of a decimal number between 0 and 31 by playing musical notes + +## Requirement: + +Write a program that asks the user to enter a decimal number between 0 and 31 +as the input and plays the 5 musical notes representing that number in binary +(a high note for dots showing and a low note for dots not showing). + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +|-------|------------------------------| +| 11 | low, high, low, high, high | +| 31 | high, high, high, high, high | +| 0 | low, low, low, low, low | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-number/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-number/scratch-expected.md new file mode 100644 index 000000000..b924ca038 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-number/scratch-expected.md @@ -0,0 +1,38 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/158861343/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Please enter a decimal number between 0 and 31:] and wait +``` + +```scratch:split:random +repeat until <(bit value) = [1]> +end + +if <<(decimal number) > (bit value)> or <(decimal number) = (bit value)>> then +else +end +``` + +```scratch:split:random +play note (72 v) for (0.5) beats + +play note (48 v) for (0.5) beats +``` + +```scratch:split:random +set [decimal number v] to (answer) + +set [bit value v] to [32] + +set [bit value v] to ((bit value) / (2)) + +set [decimal number v] to ((decimal number) - (bit value)) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-number/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-number/scratch-hints.md new file mode 100644 index 000000000..615007b5e --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-number/scratch-hints.md @@ -0,0 +1,17 @@ +- Make variables called: + + - “decimal number” and set its value to the input number given by the + end user. + - “bit value” and set its value to ‘32’. + +- Now divide the “bit value” by 2 and check if “decimal number” is greater + than or equal to “bit value”. + If it is, play a high note and subtract “bit value” from the “decimal + number”. + If not, play a low note. + Repeat until “bit value” is equal to 1. +- To play a musical note use the block + `scratch:play note (60 v) for (0.5) beats` beats under the “Sound”. + Type in the number “72” for a high note and “48” for a low note. +- Test your program with some values on the boundaries (for example test it + with numbers 0 and 31). diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-number/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-number/scratch-solution.md new file mode 100644 index 000000000..17169a812 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-notes-representing-number/scratch-solution.md @@ -0,0 +1,15 @@ +```scratch +when green flag clicked +ask [Please enter a decimal number between 0 and 31:] and wait +set [decimal number v] to (answer) +set [bit value v] to [32] +repeat until <(bit value) = [1]> + set [bit value v] to ((bit value) / (2)) + if <<(decimal number) > (bit value)> or <(decimal number) = (bit value)>> then + play note (72 v) for (0.5) beats + set [decimal number v] to ((decimal number) - (bit value)) + else + play note (48 v) for (0.5) beats + end +end +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/binary-numbers-no-calculations.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/binary-numbers-no-calculations.md new file mode 100644 index 000000000..5c06a4ff5 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/binary-numbers-no-calculations.md @@ -0,0 +1,10 @@ +# Display binary numbers (without calculations) + +## Requirement: + +Create a program to display numbers 1, 2, 4, 8 and 16 on the screen, one at a +time. + +## Testing examples: + +The output will always show the numbers 1, 2, 4, 8 and 16 (there is no input). diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/extra-challenge.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/extra-challenge.md new file mode 100644 index 000000000..92e8f7ba2 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/extra-challenge.md @@ -0,0 +1 @@ +Display the numbers from largest to smallest (display numbers 16, 8, 4, 2 and 1). diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/python-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/python-expected.md new file mode 100644 index 000000000..8f7dd17e4 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/python-expected.md @@ -0,0 +1,7 @@ +``` +1 +2 +4 +8 +16 +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/python-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/python-hints.md new file mode 100644 index 000000000..fd61dfe09 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/python-hints.md @@ -0,0 +1,5 @@ +- The `print()` statement allows you to display text or values within the + parentheses on the screen. + For example: `print(100)` would display `100` on the screen. +- You will need to use a `print()` statement for each number you wish to + display. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/python-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/python-solution.md new file mode 100644 index 000000000..4b6ebe74a --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/python-solution.md @@ -0,0 +1,7 @@ +```python +print(1) +print(2) +print(4) +print(8) +print(16) +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/scratch-expected.md new file mode 100644 index 000000000..32e294884 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/scratch-expected.md @@ -0,0 +1,21 @@ +Click on the green flag to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148423714/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when flag clicked + +say [1] for (1) secs + +say [2] for (1) secs + +say [4] for (1) secs + +say [8] for (1) secs + +say [16] for (1) secs +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/scratch-hints.md new file mode 100644 index 000000000..abd333877 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/scratch-hints.md @@ -0,0 +1,12 @@ +- Click inside the `scratch:say [Hello] for (2) secs` block and type to change + “Hello” to what you want to display on the the screen. + In this case, it will be ‘1’ for the first output, ‘2’ for the second, ‘4’ for + the third and so on. + +- The number of seconds tells the output how long to show. + The script waits that long before continuing. + +- Make sure all your blocks are “snapped” together in a line like a jigsaw puzzle. + +- Whenever you click the green flag, your script will start. + To stop, click the stop button. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/scratch-solution.md new file mode 100644 index 000000000..92b04e681 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-no-calculations/scratch-solution.md @@ -0,0 +1,8 @@ +```scratch +when green flag clicked +say [1] for (1) secs +say [2] for (1) secs +say [4] for (1) secs +say [8] for (1) secs +say [16] for (1) secs +``` \ No newline at end of file diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-one-line/binary-numbers-one-line.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-one-line/binary-numbers-one-line.md new file mode 100644 index 000000000..acc64ddab --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-one-line/binary-numbers-one-line.md @@ -0,0 +1,14 @@ +# Display binary numbers (in one line of output) + +## Requirement: + +Write a program to display numbers 1, 2, 4, 8 and 16 on the screen, all in +one line of output. +You need to use two variables, one for storing the number of dots +(i.e. 1, 2, 4, 8 and 16) and one to store a the line of output +(i.e. “1, 2, 4, 8, 16”). + +## Testing examples: + +The output will always show the numbers 1, 2, 4, 8 and 16 in one line (there +is no input). diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-one-line/extra-challenge.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-one-line/extra-challenge.md new file mode 100644 index 000000000..98bffa4e9 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-one-line/extra-challenge.md @@ -0,0 +1 @@ +Now try removing the last `,` from the end of your output. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-one-line/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-one-line/scratch-expected.md new file mode 100644 index 000000000..b663ce32f --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-one-line/scratch-expected.md @@ -0,0 +1,26 @@ +Click on the green flag to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148423886/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +set [number of dots v] to [1] + +set [binary cards v] to [] + +set [binary cards v] to (join (binary cards) (join (number of dots) [, ])) + +set [number of dots v] to ((number of dots) * (2)) +``` + +```scratch:split:random +when green flag clicked + +say (binary cards) + +repeat (5) +end +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-one-line/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-one-line/scratch-hints.md new file mode 100644 index 000000000..6c9ab4900 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-one-line/scratch-hints.md @@ -0,0 +1,18 @@ +- Make variables called: + + - “number of dots” to store the number of dots 1, 2, 4, 8, 16 where each + number is calculated by multiplying the previous number by 2. + - “binary cards” to store a set of strings “1, 2, 4, 8, 16,” and displays it + as the output. + +- Use the `scratch:(join [hello] [world])` block under “Operators” to + combine two strings. You can use multiple join blocks inside each other if + you need to combine more than two strings. +- In Scratch, to concatenate data together you use the + `scratch:(join [hello] [world])` blocks. + This can be used to join text, variables and calculations. + Remember to check if the spaces are correct between the items being joined! +- Use the `scratch:repeat()` block to run the blocks inside a specified number + of times. + In this challenge you need to repeat the blocks 5 times (4 times for the + extra challenge). diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-one-line/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-one-line/scratch-solution.md new file mode 100644 index 000000000..c25aae641 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-one-line/scratch-solution.md @@ -0,0 +1,10 @@ +```scratch +when green flag clicked +set [number of dots v] to [1] +set [binary cards v] to [] +repeat (5) + set [binary cards v] to (join (binary cards) (join (number of dots) [, ])) + set [number of dots v] to ((number of dots) * (2)) +end +say (binary cards) +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable-operator-repeat/binary-numbers-using-variable-operator-repeat.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable-operator-repeat/binary-numbers-using-variable-operator-repeat.md new file mode 100644 index 000000000..bb1e15f71 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable-operator-repeat/binary-numbers-using-variable-operator-repeat.md @@ -0,0 +1,12 @@ +# Display Binary Numbers (using a variable, operator and a repeat loop) + +## Requirement: + +Write a program to display numbers 1, 2, 4, 8 and 16 on the screen one at a +time. +This challenge builds on the previous one, but the program should use a loop, +so it could easily be updated to display more numbers. + +## Testing examples: + +The output will always show the numbers 1, 2, 4, 8 and 16 (there is no input). diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable-operator-repeat/extra-challenge.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable-operator-repeat/extra-challenge.md new file mode 100644 index 000000000..92e8f7ba2 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable-operator-repeat/extra-challenge.md @@ -0,0 +1 @@ +Display the numbers from largest to smallest (display numbers 16, 8, 4, 2 and 1). diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable-operator-repeat/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable-operator-repeat/scratch-expected.md new file mode 100644 index 000000000..c2c4d4413 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable-operator-repeat/scratch-expected.md @@ -0,0 +1,20 @@ +Click on the green flag to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148423848/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +set [number of dots v] to [1] + +repeat (5) +end + +say (number of dots) for (1) secs + +set [number of dots v] to ((number of dots) * (2)) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable-operator-repeat/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable-operator-repeat/scratch-hints.md new file mode 100644 index 000000000..2a0a1d220 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable-operator-repeat/scratch-hints.md @@ -0,0 +1,9 @@ +- Use the `scratch:set [number of dots v] to [1]` block to set the value of + your new variable. + Use the `scratch:() * ()` operation under “Operators” to double the value of your + variable. +- Display the value of your variable on the screen by replacing “Hello” in + your `scratch:say [Hello] for (2) secs` block with your new variable. +- Use the `scratch:repeat ()` block to run the blocks inside a specified + number of times. + In this challenge you need to repeat the blocks 5 times. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable-operator-repeat/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable-operator-repeat/scratch-solution.md new file mode 100644 index 000000000..3ccf333d9 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable-operator-repeat/scratch-solution.md @@ -0,0 +1,8 @@ +```scratch +when green flag clicked +set [number of dots v] to [1] +repeat (5) + say (number of dots) for (1) secs + set [number of dots v] to ((number of dots) * (2)) +end +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/binary-numbers-using-variable.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/binary-numbers-using-variable.md new file mode 100644 index 000000000..ef9668d39 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/binary-numbers-using-variable.md @@ -0,0 +1,12 @@ +# Display Binary Numbers (using a variable) + +## Requirement: + +Write a program to display numbers 1, 2, 4, 8 and 16 on the screen one at a +time. +Instead of displaying the number directly, you should +store it into a variable, and display the variable. + +## Testing examples: + +The output will always show the numbers 1, 2, 4, 8 and 16 (there is no input). diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/extra-challenge.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/extra-challenge.md new file mode 100644 index 000000000..92e8f7ba2 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/extra-challenge.md @@ -0,0 +1 @@ +Display the numbers from largest to smallest (display numbers 16, 8, 4, 2 and 1). diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/python-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/python-expected.md new file mode 100644 index 000000000..8f7dd17e4 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/python-expected.md @@ -0,0 +1,7 @@ +``` +1 +2 +4 +8 +16 +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/python-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/python-hints.md new file mode 100644 index 000000000..3f917a220 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/python-hints.md @@ -0,0 +1,12 @@ +- Make a variable called `number_of_dots` and change its value by setting it + to 1, 2, 4, 8 and 16 respectively. + Make sure your variable name is meaningful for someone else to read. + In the above example, the variable is called `number_of_dots` because + it stores the number of dots on a card. +- Use `number_of_dots = 0` to set the value of your new variable. + This code would set `number_of_dots` to zero. +- The `print()` statement allows you to display text or values within the + parentheses on the screen. + For example: `print(100)` would display `100` on the screen. +- You will need to use a `print()` statement for each number you wish to + display. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/python-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/python-solution.md new file mode 100644 index 000000000..ce4f13b0d --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/python-solution.md @@ -0,0 +1,12 @@ +```python +number_of_dots = 1 +print(number_of_dots) +number_of_dots = 2 +print(number_of_dots) +number_of_dots = 4 +print(number_of_dots) +number_of_dots = 8 +print(number_of_dots) +number_of_dots = 16 +print(number_of_dots) +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/scratch-expected.md new file mode 100644 index 000000000..21196dcae --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/scratch-expected.md @@ -0,0 +1,35 @@ +Click on the green flag to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148423794/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch +when green flag clicked +``` + +```scratch:split:random +set [number of dots v] to [1] + +set [number of dots v] to [2] + +set [number of dots v] to [4] + +set [number of dots v] to [8] + +set [number of dots v] to [16] +``` + +```scratch:split:random +say (number of dots) for (1) secs + +say (number of dots) for (1) secs + +say (number of dots) for (1) secs + +say (number of dots) for (1) secs + +say (number of dots) for (1) secs +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/scratch-hints.md new file mode 100644 index 000000000..81692516d --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/scratch-hints.md @@ -0,0 +1,17 @@ +- Make a variable called “number of dots” and change its value by setting it + to 1, 2, 4, 8 and 16 respectively. + You can find “make a new variable” under the “data” script to create a new + variable. + Make sure your variable name is meaningful for someone else to read. + In the above example, the variable is called "number of dots" because + it stores the number of dots on a card. +- Use the `scratch:set [number of dots v] to [0]` block to set the value of + your new variable. +- Display the value of your variable on the screen by replacing “Hello” in + your `scratch:say [Hello] for (2) secs` block with your variable. +- The number of seconds tells the output how long to show. + The script waits that long before continuing. +- Make sure all your blocks are “snapped” together in a line like a jigsaw + puzzle. +- Whenever you click the green flag, your script will start. + To stop, click the stop button. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/scratch-solution.md new file mode 100644 index 000000000..c57da7e11 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variable/scratch-solution.md @@ -0,0 +1,13 @@ +```scratch +when green flag clicked +set [number of dots v] to [1] +say (number of dots) for (1) secs +set [number of dots v] to [2] +say (number of dots) for (1) secs +set [number of dots v] to [4] +say (number of dots) for (1) secs +set [number of dots v] to [8] +say (number of dots) for (1) secs +set [number of dots v] to [16] +say (number of dots) for (1) secs +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/binary-numbers-using-variables-as-operator.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/binary-numbers-using-variables-as-operator.md new file mode 100644 index 000000000..04aeb7de2 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/binary-numbers-using-variables-as-operator.md @@ -0,0 +1,14 @@ +# Display Binary Numbers (using variables as an operator) + +## Requirement: + +Write a program to display numbers 1, 2, 4, 8 and 16 on the screen one at a +time. +This is similar to the previous challenge, but instead of storing a number +directly into the variable, you should double the value in the variable each +time. +You need to use an operator (for multiplying 2 numbers) for this challenge. + +## Testing examples: + +The output will always show the numbers 1, 2, 4, 8 and 16 (there is no input). diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/extra-challenge.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/extra-challenge.md new file mode 100644 index 000000000..8e1526f92 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/extra-challenge.md @@ -0,0 +1 @@ +Display the numbers from largest to smallest (display the numbers 16, 8, 4, 2 and 1). diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/python-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/python-expected.md new file mode 100644 index 000000000..8f7dd17e4 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/python-expected.md @@ -0,0 +1,7 @@ +``` +1 +2 +4 +8 +16 +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/python-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/python-hints.md new file mode 100644 index 000000000..b5db6bd1e --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/python-hints.md @@ -0,0 +1,9 @@ +- Make a variable called `number_of_dots` and set its value to 1. +- To multiply a number in Python, use the `*` symbol. + For example: `2 * 3` is equal to `6`. +- To double a variable's value, set the variable to be itself multiplied by 2. + For example: If you have the variable `age` and it is set to `5`, you can + double the variable with `age = age * 2`. +- The `print()` statement allows you to display text or values within the + parentheses on the screen. + For example: `print(100)` would display `100` on the screen. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/python-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/python-solution.md new file mode 100644 index 000000000..7008473bb --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/python-solution.md @@ -0,0 +1,12 @@ +```python +number_of_dots = 1 +print(number_of_dots) +number_of_dots = number_of_dots * 2 +print(number_of_dots) +number_of_dots = number_of_dots * 2 +print(number_of_dots) +number_of_dots = number_of_dots * 2 +print(number_of_dots) +number_of_dots = number_of_dots * 2 +print(number_of_dots) +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/scratch-expected.md new file mode 100644 index 000000000..2241a5231 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/scratch-expected.md @@ -0,0 +1,35 @@ +Click on the green flag to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148423827/?autostart=false"} + +{panel type="teacher-note" title="Recommended blocks"} + +```scratch +when flag clicked +``` + +```scratch:split:random +set [number of dots v] to [1] + +set [number of dots v] to ((number of dots) * (2)) + +set [number of dots v] to ((number of dots) * (2)) + +set [number of dots v] to ((number of dots) * (2)) + +set [number of dots v] to ((number of dots) * (2)) +``` + +```scratch:split +say (number of dots) for (1) secs + +say (number of dots) for (1) secs + +say (number of dots) for (1) secs + +say (number of dots) for (1) secs + +say (number of dots) for (1) secs +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/scratch-hints.md new file mode 100644 index 000000000..1062aa651 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/scratch-hints.md @@ -0,0 +1,12 @@ +- Make a variable called “number of dots” and change its value by using an + operator (multiplying it by 2 before displaying it on the screen each time). + You can find “make a new variable” under “data” script to create a new + variable. + Make sure your variable name is as descriptive as possible. +- Use the `scratch:set [number of dots v] to [1]` block to set the value of + your new variable. + Use the `scratch:() * ()` operation under “Operators” to double the value of + your variable. +- Display the value of your variable on the screen by replacing + “Hello” in your `scratch:say [Hello] for (2) secs` block with your new + variable. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/scratch-solution.md new file mode 100644 index 000000000..ccfd1a63b --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/binary-numbers-using-variables-as-operator/scratch-solution.md @@ -0,0 +1,13 @@ +```scratch +when green flag clicked +set [number of dots v] to [1] +say (number of dots) for (1) secs +set [number of dots v] to ((number of dots) * (2)) +say (number of dots) for (1) secs +set [number of dots v] to ((number of dots) * (2)) +say (number of dots) for (1) secs +set [number of dots v] to ((number of dots) * (2)) +say (number of dots) for (1) secs +set [number of dots v] to ((number of dots) * (2)) +say (number of dots) for (1) secs +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number-one-line/cards-given-number-one-line.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number-one-line/cards-given-number-one-line.md new file mode 100644 index 000000000..2090bdc6d --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number-one-line/cards-given-number-one-line.md @@ -0,0 +1,20 @@ +# Display the binary cards needed to represent a given number of dots (all in one line) + +## Requirement: + +Write a program that asks the user to enter a number of dots less than or +equal to 31 as the input and displays which of the 5 cards should be showing +as the output all in one line. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided; + +| Input | Output | +|-------|--------------------------------------------------| +| 15 | 8, 4, 2, 1, | +| 1 | 1, | +| 8 | 8, | +| 31 | 16, 8, 4, 2, 1, | +| 32 | Please choose a number less than or equal to 31. | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number-one-line/extra-challenge.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number-one-line/extra-challenge.md new file mode 100644 index 000000000..98bffa4e9 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number-one-line/extra-challenge.md @@ -0,0 +1 @@ +Now try removing the last `,` from the end of your output. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number-one-line/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number-one-line/scratch-expected.md new file mode 100644 index 000000000..cb93106f7 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number-one-line/scratch-expected.md @@ -0,0 +1,67 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148424217/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Please enter a number of dots less than or equal to 31:] and wait +``` + +```scratch:split:random +if <<(number of dots) > [16]> or <(number of dots) = [16]>> then +end + +if <<(number of dots) > [8]> or <(number of dots) = [8]>> then +end + +if <<(number of dots) > [4]> or <(number of dots) = [4]>> then +end + +if <<(number of dots) > [2]> or <(number of dots) = [2]>> then +end + +if <<(number of dots) > [1]> or <(number of dots) = [1]>> then +end + +if <<(number of dots) < [31]> or <(number of dots) = [31]>> then +else +end +``` + +```scratch:split:random +set [number of dots v] to (answer) + +set [cards v] to [] + +set [cards v] to (join (cards) [16, ]) + +set [number of dots v] to ((number of dots) - (16)) + +set [cards v] to (join (cards) [8, ]) + +set [number of dots v] to ((number of dots) - (8)) + +set [cards v] to (join (cards) [4, ]) + +set [number of dots v] to ((number of dots) - (4)) + +set [cards v] to (join (cards) [2, ]) + +set [number of dots v] to ((number of dots) - (2)) + +set [cards v] to (join (cards) [1, ]) + +set [number of dots v] to ((number of dots) - (1)) + +``` + +```scratch:split:random +say (cards) + +say [Please choose a number less than or equal to 31.] +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number-one-line/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number-one-line/scratch-hints.md new file mode 100644 index 000000000..51fa0162a --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number-one-line/scratch-hints.md @@ -0,0 +1,42 @@ +- Make a variable called “number of dots” and set its value to the input + entered by the end user. + Make another variable called “cards”. + Below is the algorithm to help you program this: + +``` +If “number of dots” less than or equal to 31 + If “number of dots” greater than or equal to 16 + Subtract 16 from “number of dots” and add “16, ” to string variable “cards” + If “number of dots” greater than or equal to 8 + Subtract 8 from “number of dots” and add “8, ” to string variable “cards” + If “number of dots” greater than or equal to 4 + Subtract 4 from “number of dots” and add “4, ” to string variable “cards” + If “number of dots” greater than or equal to 2 + Subtract 2 from “number of dots” and add “2, ” to string variable “cards” + If “number of dots” greater than or equal to 1 + Subtract 1 from “number of dots” and add “1, ” to string variable “cards” + Display the value of “cards” +else + Ask the end user to enter a number less than or equal to “31” +``` + +- The `scratch:if <> then` block checks if the condition is true and then + runs the blocks inside of the IF block. + In this challenge you need to use `scratch:if <> then` blocks to check if + the “number of dots” is greater than or equal to 16 (use the + `scratch:<> or <>` block under “Operators”). + If it is, display card “16” and subtract 16 from your number and do the + same with the rest of the cards. +- You can also use the IF THEN ELSE block (see image below) if you need to run + a set of blocks if the condition is not true. + +```scratch +if <> then +else +end +``` + +- In the extra challenge you need to ask the user to enter a number less + than or equal to 31 if the input which was entered was greater than 31. +- Test your program with some values on the boundaries (i.e. 32, 31, 16, 8, + 4, 2, 1) diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number-one-line/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number-one-line/scratch-solution.md new file mode 100644 index 000000000..1084b6048 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number-one-line/scratch-solution.md @@ -0,0 +1,31 @@ +```scratch +when green flag clicked +ask [Please enter a number of dots less than or equal to 31:] and wait +set [number of dots v] to (answer) +set [cards v] to [] +if <<(number of dots) < [31]> or <(number of dots) = [31]>> then + if <<(number of dots) > [16]> or <(number of dots) = [16]>> then + set [cards v] to (join (cards) [16, ]) + set [number of dots v] to ((number of dots) - (16)) + end + if <<(number of dots) > [8]> or <(number of dots) = [8]>> then + set [cards v] to (join (cards) [8, ]) + set [number of dots v] to ((number of dots) - (8)) + end + if <<(number of dots) > [4]> or <(number of dots) = [4]>> then + set [cards v] to (join (cards) [4, ]) + set [number of dots v] to ((number of dots) - (4)) + end + if <<(number of dots) > [2]> or <(number of dots) = [2]>> then + set [cards v] to (join (cards) [2, ]) + set [number of dots v] to ((number of dots) - (2)) + end + if <<(number of dots) > [1]> or <(number of dots) = [1]>> then + set [cards v] to (join (cards) [1, ]) + set [number of dots v] to ((number of dots) - (1)) + end + say (cards) +else + say [Please choose a number less than or equal to 31.] +end +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number/cards-given-number.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number/cards-given-number.md new file mode 100644 index 000000000..01e73cc97 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number/cards-given-number.md @@ -0,0 +1,20 @@ +# Display the binary cards needed to represent a given number of dots + +## Requirement: + +Write a program that asks the user to enter a number of dots less than or +equal to 31 as the input and displays which of the 5 cards should be showing +as the output (one at a time). + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +|-------|--------------------------------------------------| +| 11 | 8
2
1 | +| 1 | 1 | +| 8 | 8 | +| 31 | 16
8
4
2
1 | +| 32 | Please choose a number less than or equal to 31. | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number/scratch-expected.md new file mode 100644 index 000000000..1c4beea6e --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number/scratch-expected.md @@ -0,0 +1,62 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148424197/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Please enter a number of dots less than or equal to 31:] and wait +``` + +```scratch:split:random +if <<(number of dots) < [31]> or <(number of dots) = [31]>> then +else +end + +if <<(number of dots) > [16]> or <(number of dots) = [16]>> then +end + +if <<(number of dots) > [8]> or <(number of dots) = [8]>> then +end + +if <<(number of dots) > [4]> or <(number of dots) = [4]>> then +end + +if <<(number of dots) > [2]> or <(number of dots) = [2]>> then +end + +if <<(number of dots) > [1]> or <(number of dots) = [1]>> then +end +``` + +```scratch:split:random +set [number of dots v] to (answer) + +set [number of dots v] to ((number of dots) - (16)) + +set [number of dots v] to ((number of dots) - (8)) + +set [number of dots v] to ((number of dots) - (4)) + +set [number of dots v] to ((number of dots) - (2)) + +set [number of dots v] to ((number of dots) - (1)) +``` + +```scratch:split:random +say [Please choose a number less than or equal to 31.] + +say [16] for (1) secs + +say [8] for (1) secs + +say [4] for (1) secs + +say [2] for (1) secs + +say [1] for (1) secs +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number/scratch-hints.md new file mode 100644 index 000000000..1d1970d85 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number/scratch-hints.md @@ -0,0 +1,37 @@ +- Make a variable called “number of dots” and set its value to the input + entered by the end user. + Below is the algorithm to help you program this: + +``` +If “number of dots” less than or equal to 31 + If “number of dots” greater than or equal to 16 + Subtract 16 from “number of dots” and display “16” + If “number of dots” greater than or equal to 8 + Subtract 8 from “number of dots” and display “8” + If “number of dots” greater than or equal to 4 + Subtract 4 from “number of dots” and display “4” + If “number of dots” greater than or equal to 2 + Subtract 2 from “number of dots” and display “2” + If “number of dots” greater than or equal to 1 + Subtract 1 from “number of dots” and display “1” +else + Ask the end user to enter a number less than or equal to “31” +``` + +- The `scratch:if <> then` block checks if the condition is true and then + runs the blocks inside of the IF block. + In this challenge you need to use IF statements to check if the “number of + dots” is greater than or equal to 16 (use the OR block under “Operators”). + If it is, display card “16” and subtract 16 from your number and do the + same with the rest of the cards. +- You can also use the IF THEN ELSE block (see image below) if you need to run + a set of blocks if the condition is not true. + +```scratch +if <> then +else +end +``` + +- Test your program with some values on the boundaries (i.e. 32, 31, 16, + 8, 4, 2, 1) diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number/scratch-solution.md new file mode 100644 index 000000000..029bfe5b9 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/cards-given-number/scratch-solution.md @@ -0,0 +1,29 @@ +```scratch +when green flag clicked +ask [Please enter a number of dots less than or equal to 31:] and wait +set [number of dots v] to (answer) +if <<(number of dots) < [31]> or <(number of dots) = [31]>> then + if <<(number of dots) > [16]> or <(number of dots) = [16]>> then + set [number of dots v] to ((number of dots) - (16)) + say [16] for (1) secs + end + if <<(number of dots) > [8]> or <(number of dots) = [8]>> then + set [number of dots v] to ((number of dots) - (8)) + say [8] for (1) secs + end + if <<(number of dots) > [4]> or <(number of dots) = [4]>> then + set [number of dots v] to ((number of dots) - (4)) + say [4] for (1) secs + end + if <<(number of dots) > [2]> or <(number of dots) = [2]>> then + set [number of dots v] to ((number of dots) - (2)) + say [2] for (1) secs + end + if <<(number of dots) > [1]> or <(number of dots) = [1]>> then + set [number of dots v] to ((number of dots) - (1)) + say [1] for (1) secs + end +else + say [Please choose a number less than or equal to 31.] +end +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-no-loop/count-5-bw-no-loop.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-no-loop/count-5-bw-no-loop.md new file mode 100644 index 000000000..c2b486643 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-no-loop/count-5-bw-no-loop.md @@ -0,0 +1,18 @@ +# Count dots on 5 black and white cards (without a loop) + +## Requirement: + +Write a program that asks the user to enter 5 black and white cards +representing bits ('B' for black and 'W' for white which are entered one at a +time) and displays the total number of dots as the output. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +|-----------------------|--------| +| B
W
B
B
W | 9 | +| B
B
B
B
B | 0 | +| W
W
W
W
W | 31 | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-no-loop/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-no-loop/scratch-expected.md new file mode 100644 index 000000000..4dc6d6f21 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-no-loop/scratch-expected.md @@ -0,0 +1,56 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148424082/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +say (total number of dots) +``` + +```scratch:split:random +if <(answer) = [W]> then +end + +if <(answer) = [W]> then +end + +if <(answer) = [W]> then +end + +if <(answer) = [W]> then +end + +if <(answer) = [W]> then +end +``` + +```scratch:split:random +ask [What's your first card (B for black, W for white)?] and wait + +ask [What's your second card (B for black, W for white)?] and wait + +ask [What's your third card (B for black, W for white)?] and wait + +ask [What's your fourth card (B for black, W for white)?] and wait + +ask [What's your fifth card (B for black, W for white)?] and wait +``` + +```scratch:split:random +set [total number of dots v] to [0] + +set [total number of dots v] to ((total number of dots) + (16)) + +set [total number of dots v] to ((total number of dots) + (8)) + +set [total number of dots v] to ((total number of dots) + (4)) + +set [total number of dots v] to ((total number of dots) + (2)) + +set [total number of dots v] to ((total number of dots) + (1)) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-no-loop/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-no-loop/scratch-hints.md new file mode 100644 index 000000000..82264e50a --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-no-loop/scratch-hints.md @@ -0,0 +1,11 @@ +- Make a variable called “total number of dots” and set it to 0. + Add the corresponding number of dots (16 for the first input, 8 for the + second input and so on) to the “total number of dots” every time the end + user enters ‘W’ as the input. + You would need to use 5 `scratch:if <> then` blocks for this challenge. +- Use the `scratch:set [] to [0]` block to set the value of your new + variable. + Use the `scratch:() + ()` operation under “Operators” to add to the + value of your variable. +- The `scratch:if <> then` block checks if the condition is true and then + runs the blocks inside of the if block. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-no-loop/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-no-loop/scratch-solution.md new file mode 100644 index 000000000..66a759bc2 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-no-loop/scratch-solution.md @@ -0,0 +1,25 @@ +```scratch +when green flag clicked +set [total number of dots v] to [0] +ask [What's your first card (B for black, W for white)?] and wait +if <(answer) = [W]> then + set [total number of dots v] to ((total number of dots) + (16)) +end +ask [What's your second card (B for black, W for white)?] and wait +if <(answer) = [W]> then + set [total number of dots v] to ((total number of dots) + (8)) +end +ask [What's your third card (B for black, W for white)?] and wait +if <(answer) = [W]> then + set [total number of dots v] to ((total number of dots) + (4)) +end +ask [What's your fourth card (B for black, W for white)?] and wait +if <(answer) = [W]> then + set [total number of dots v] to ((total number of dots) + (2)) +end +ask [What's your fifth card (B for black, W for white)?] and wait +if <(answer) = [W]> then + set [total number of dots v] to ((total number of dots) + (1)) +end +say (total number of dots) +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-no-loop/count-5-bw-one-input-no-loop.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-no-loop/count-5-bw-one-input-no-loop.md new file mode 100644 index 000000000..1afd9bd57 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-no-loop/count-5-bw-one-input-no-loop.md @@ -0,0 +1,20 @@ +# Count dots on 5 black and white cards as one input (without a loop) + +## Requirement: + +Write a program that asks the end user to enter 5 black and white cards +representing bits, all as one line of input ('B' for black and 'W' for white), +and displays the total number of dots as the output. +You can do this without a loop, using 5 similar sets of blocks to process each +of the 5 values. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +|-----------------------|--------| +| W
W
B
B
B | 24 | +| B
B
B
B
B | 0 | +| W
W
W
W
W | 31 | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-no-loop/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-no-loop/scratch-expected.md new file mode 100644 index 000000000..f0f51060b --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-no-loop/scratch-expected.md @@ -0,0 +1,49 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148424155/?autostart=false"} + + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Please enter 5 cards ('B' for black and 'W' for white):] and wait + +say (total number of dots) +``` + +```scratch:split:random +if <(letter (1) of (cards)) = [W]> then +end + +if <(letter (2) of (cards)) = [W]> then +end + +if <(letter (3) of (cards)) = [W]> then +end + +if <(letter (4) of (cards)) = [W]> then +end + +if <(letter (5) of (cards)) = [W]> then +end +``` + +```scratch:split:random +set [total number of dots v] to [0] + +set [cards v] to (answer) + +set [total number of dots v] to ((total number of dots) + (16)) + +set [total number of dots v] to ((total number of dots) + (8)) + +set [total number of dots v] to ((total number of dots) + (4)) + +set [total number of dots v] to ((total number of dots) + (2)) + +set [total number of dots v] to ((total number of dots) + (1)) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-no-loop/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-no-loop/scratch-hints.md new file mode 100644 index 000000000..55bac853b --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-no-loop/scratch-hints.md @@ -0,0 +1,10 @@ +- Make a variable called “total number of dots” and set its value to 0. + Make a string variable called “cards” and set its value to the input + entered by the end user (5 black and white cards). + Check each letter of the string and if it’s ‘W’ add the corresponding + number of dots (16 for the first letter, 8 for the second letter and so + on) to the “total number of dots”. + Display the “total number of dots” as the output. +- You can access a letter at the specified position in a string by using + the `scratch:letter (1) of [world]` block under “Operators”. + For example: `scratch:letter (1) of [world] //w` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-no-loop/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-no-loop/scratch-solution.md new file mode 100644 index 000000000..0ef064159 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-no-loop/scratch-solution.md @@ -0,0 +1,22 @@ +```scratch +when green flag clicked +set [total number of dots v] to [0] +ask [Please enter 5 cards ('B' for black and 'W' for white):] and wait +set [cards v] to (answer) +if <(letter (1) of (cards)) = [W]> then + set [total number of dots v] to ((total number of dots) + (16)) +end +if <(letter (2) of (cards)) = [W]> then + set [total number of dots v] to ((total number of dots) + (8)) +end +if <(letter (3) of (cards)) = [W]> then + set [total number of dots v] to ((total number of dots) + (4)) +end +if <(letter (4) of (cards)) = [W]> then + set [total number of dots v] to ((total number of dots) + (2)) +end +if <(letter (5) of (cards)) = [W]> then + set [total number of dots v] to ((total number of dots) + (1)) +end +say (total number of dots) +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-using-loop/count-5-bw-one-input-using-loop.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-using-loop/count-5-bw-one-input-using-loop.md new file mode 100644 index 000000000..06e035759 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-using-loop/count-5-bw-one-input-using-loop.md @@ -0,0 +1,20 @@ +# Count dots on 5 black and white cards as one input (using a loop) + +## Requirement: + +Write a program that asks the end user to enter 5 black and white cards +representing bits, all as one input ('B' for black and 'W' for white), and +displays the total number of dots as the output. +Use a loop for this challenge, so it could be changed to work for a different +number of cards. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +|-----------------------|--------| +| B
B
B
B
W | 1 | +| B
B
B
B
B | 0 | +| W
W
W
W
W | 31 | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-using-loop/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-using-loop/scratch-expected.md new file mode 100644 index 000000000..1edd910bf --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-using-loop/scratch-expected.md @@ -0,0 +1,37 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148424160/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Please enter 5 cards (B for black and W for white):] and wait + +repeat (5) +end + +if <(letter (index) of (cards)) = [W]> then +end + +change [index v] by (1) + +say (total number of dots) +``` + +```scratch:split:random +set [total number of dots v] to [0] + +set [index v] to [1] + +set [number of dots v] to [16] + +set [cards v] to (answer) + +set [total number of dots v] to ((total number of dots) + (number of dots)) + +set [number of dots v] to ((number of dots) / (2)) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-using-loop/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-using-loop/scratch-hints.md new file mode 100644 index 000000000..99cd15d43 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-using-loop/scratch-hints.md @@ -0,0 +1,21 @@ +- Make variables called: + + - “total number of dots” and set its value to 0. + - “number of dots” and set its value to 16. + - “cards” and set its value to the string entered as the input. + Check each letter of the input string “cards” (use a loop to iterate + 5 times) starting from the first letter. + If it’s ‘W’ add the corresponding number of dots (16 for the first + letter, 8 for the second letter and so on) to the “total number + of dots”. + Display the “total number of dots” as the output. + - “index” and set its value to 1. + Use this variable to access a letter at the “index” position in the + string. + +- You can access a letter at the specified position in a string by using + the `scratch:letter (1) of [world]` block under “Operators”. + For example: `scratch:letter (1) of [world] //w` + +- In this challenge you need to access all the 5 letters in user’s input and + check to see if each of them is equal to B (black) or W (white). diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-using-loop/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-using-loop/scratch-solution.md new file mode 100644 index 000000000..2347bdf46 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-one-input-using-loop/scratch-solution.md @@ -0,0 +1,16 @@ +```scratch +when green flag clicked +set [total number of dots v] to [0] +set [index v] to [1] +set [number of dots v] to [16] +ask [Please enter 5 cards (B for black and W for white):] and wait +set [cards v] to (answer) +repeat (5) + if <(letter (index) of (cards)) = [W]> then + set [total number of dots v] to ((total number of dots) + (number of dots)) + end + set [number of dots v] to ((number of dots) / (2)) + change [index v] by (1) +end +say (total number of dots) +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-using-loop/count-5-bw-using-loop.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-using-loop/count-5-bw-using-loop.md new file mode 100644 index 000000000..c5bb04e3b --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-using-loop/count-5-bw-using-loop.md @@ -0,0 +1,20 @@ +# Count dots on 5 black and white cards (using a loop) + +## Requirement: + +Write a program that asks the end user to enter 5 black and white cards +representing bits ('B' for black and 'W' for white which are entered one at a +time) and displays the total number of dots as the output. +Use a loop for this challenge, so it could be changed to work for a different +number of cards. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +|-----------------------|--------| +| B
W
W
B
W | 13 | +| B
B
B
B
B | 0 | +| W
W
W
W
W | 31 | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-using-loop/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-using-loop/scratch-expected.md new file mode 100644 index 000000000..e16edc111 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-using-loop/scratch-expected.md @@ -0,0 +1,31 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148424135/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +repeat (5) +end + +say (total number of dots) + +ask [Please enter a black or white card ('B' for black or 'W' for white):] and wait + +if <(answer) = [W]> then +end +``` + +```scratch:split:random +set [number of dots v] to ((number of dots) / (2)) + +set [total number of dots v] to ((total number of dots) + (number of dots)) + +set [number of dots v] to [16] + +set [total number of dots v] to [0] +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-using-loop/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-using-loop/scratch-hints.md new file mode 100644 index 000000000..7f106b420 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-using-loop/scratch-hints.md @@ -0,0 +1,11 @@ +- Make a variable called “total number of dots” and set its value to 0. + Make another variable called “number of dots” and set its value to 16. + Add the value of “number of dots” to the value of “total number of dots” + every time the end user enters ‘W’ as the input. + Repeat this 5 times dividing the value of “number of dots” by 2 each time. + Display the “total number of dots” as the output. +- Use the `scratch:repeat ()` block to run the blocks inside a specified + number of times. + In this challenge you need to repeat the blocks 5 times. +- The `scratch:if <> then` block checks if the condition is true and then + runs the blocks inside of the if block. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-using-loop/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-using-loop/scratch-solution.md new file mode 100644 index 000000000..1c8f50466 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-5-bw-using-loop/scratch-solution.md @@ -0,0 +1,13 @@ +```scratch +when green flag clicked +set [number of dots v] to [16] +set [total number of dots v] to [0] +repeat (5) + ask [Please enter a black or white card ('B' for black or 'W' for white):] and wait + if <(answer) = [W]> then + set [total number of dots v] to ((total number of dots) + (number of dots)) + end + set [number of dots v] to ((number of dots) / (2)) +end +say (total number of dots) +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-bw-one-input-using-loop/count-bw-one-input-using-loop.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-bw-one-input-using-loop/count-bw-one-input-using-loop.md new file mode 100644 index 000000000..1cd2429f2 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-bw-one-input-using-loop/count-bw-one-input-using-loop.md @@ -0,0 +1,19 @@ +# Count dots on any number of black and white cards as one input (using a loop) + +## Requirement: + +Write a program that asks the user to enter any number of black and white +cards representing bits, all as one input ('B' for black and 'W' for white), +and displays the total number of dots as the output. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +|--------------------------------------|--------| +| W
B
B | 4 | +| W | 1 | +| B | 0 | +| W
W
W
W
W
W
W
W | 255 | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-bw-one-input-using-loop/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-bw-one-input-using-loop/scratch-expected.md new file mode 100644 index 000000000..cf92e7baf --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-bw-one-input-using-loop/scratch-expected.md @@ -0,0 +1,38 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148424178/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Please enter a sequence of cards ('B' for black and 'W' for white):] and wait + +if <(letter (index) of (cards)) = [W]> then +end + +repeat (length of (cards)) +end + +say (total number of dots) +``` + + +```scratch:split:random +set [total number of dots v] to [0] + +set [number of dots v] to [1] + +set [cards v] to (answer) + +set [index v] to (length of (cards)) + +set [total number of dots v] to ((total number of dots) + (number of dots)) + +set [number of dots v] to ((number of dots) * (2)) + +set [index v] to ((index) - (1)) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-bw-one-input-using-loop/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-bw-one-input-using-loop/scratch-hints.md new file mode 100644 index 000000000..d19fa589a --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-bw-one-input-using-loop/scratch-hints.md @@ -0,0 +1,29 @@ +- Make variables called: + + - “total number of dots” and set its value to 0. + - “number of dots” and set its value to 1. + - “cards” and set its value to the string entered as the input. + Check each letter of the input string “cards” (use a loop to iterate + the number of letters in the input) starting from the last letter. + If it’s ‘W’ add the corresponding number of dots (1 for the last + letter, 2 for the second to last letter and so on) to the “total + number of dots”. + Display the “total number of dots” as the output. + - “index” and set its value to the number of letters in the input. + Use this variable to access a letter at the “index” position in the + string. + +- You can access a letter at the specified position in a string by using + the `scratch:letter (1) of [world]` block under “Operators”. + For example: `scratch:letter (1) of [world] //w` + +- In this challenge you need to access all the letters in user’s input and + check to see if each of them is equal to B (black) or W (white). + +- You can find how many letters a string has by using the + `scratch:length of [world]` block unders “Operators”. + +- The `scratch: if <> then` block checks if the condition is true and then + runs the blocks inside of the IF block. + In this challenge you need to check if each letter of the input is equal + to ‘W’ or “B’. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-bw-one-input-using-loop/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-bw-one-input-using-loop/scratch-solution.md new file mode 100644 index 000000000..3ee722206 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/count-bw-one-input-using-loop/scratch-solution.md @@ -0,0 +1,16 @@ +```scratch +when green flag clicked +set [total number of dots v] to [0] +set [number of dots v] to [1] +ask [Please enter a sequence of cards ('B' for black and 'W' for white):] and wait +set [cards v] to (answer) +set [index v] to (length of (cards)) +repeat (length of (cards)) + if <(letter (index) of (cards)) = [W]> then + set [total number of dots v] to ((total number of dots) + (number of dots)) + end + set [number of dots v] to ((number of dots) * (2)) + set [index v] to ((index) - (1)) +end +say (total number of dots) +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary-alternative/decimal-to-binary-alternative.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary-alternative/decimal-to-binary-alternative.md new file mode 100644 index 000000000..10313f055 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary-alternative/decimal-to-binary-alternative.md @@ -0,0 +1,20 @@ +# Convert a decimal number to a binary number (alternative method) + +## Requirement: + +Using a different method from “Binary challenge 7.3”, write a program that +asks the user to enter any decimal number as the input and displays the +binary cards representing that number using '1' for dots showing and '0' for +not showing as the output (this converts the decimal number to binary). +This method generates the number from right to left by observing that an even +number has a 0 on the right, and an odd number as a 1 on the right. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +|-------|-------------------------------------------------------| +| 31 | The binary representation for the number 31 is 11111 | +| 32 | The binary representation for the number 32 is 100000 | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary-alternative/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary-alternative/scratch-expected.md new file mode 100644 index 000000000..9deb563d5 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary-alternative/scratch-expected.md @@ -0,0 +1,34 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148424337/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Please enter a decimal number:] and wait + +repeat until <(decimal number) = [1]> +end + +say (join (join (join [The binary representation for the number ] (answer)) [ is ]) (binary number)) +``` + +```scratch:split:random +set [decimal number v] to (answer) + +set [remainder v] to [0] + +set [binary number v] to [] + +set [remainder v] to ((decimal number) mod (2)) + +set [decimal number v] to ([floor v] of ((decimal number) / (2))) + +set [binary number v] to (join (remainder) (binary number)) + +set [binary number v] to (join (decimal number) (binary number)) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary-alternative/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary-alternative/scratch-hints.md new file mode 100644 index 000000000..b6f178bf0 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary-alternative/scratch-hints.md @@ -0,0 +1,26 @@ +- This approach is based on observing that the right-hand bit value is + easily identified (the remainder of the decimal number when divided by 2 + will be 1 i.e. it's an odd number). + The number can then be divided by 2, which moves all the digits one place + to the right, and so the next bit becomes the right-hand bit. +- Make variables called "decimal number", which is number that user enters + as the input, “binary number”, which is type string and it will be used + to store 0’s and 1’s that represent the binary number as the output, + and “remainder”, which stores the remainders of values for “decimal + number” divided by 2. +- Divide “decimal number” by 2. + Round down the result to the nearest integer by using the floor function + (choose the “floor” option from the drop down menu + `scratch:([sqrt v] of [9])` under the “Operators”) and set “decimal + number” to this value. + For example using “floor” function if you divide 11 by 2 would give + you ‘5’. +- Store the remainder in variable “remainder” (use the `scratch:() mod ()` + operation under “Operators” which reports the remainder from division of + first number by second number). + Combine “remainder” values using the `scratch:join [] []` block under the + “Operators” and store the result in the “binary number” variable. +- Repeat these blocks until “decimal number” is equal to ‘1’ (Use the + `scratch:repeat until <>` block under the “Control”. +- Add “decimal number” (which is now ‘1’) to “binary number” and display it + as the output. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary-alternative/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary-alternative/scratch-solution.md new file mode 100644 index 000000000..a7b8392c9 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary-alternative/scratch-solution.md @@ -0,0 +1,14 @@ +```scratch +when green flag clicked +ask [Please enter a decimal number:] and wait +set [decimal number v] to (answer) +set [remainder v] to [0] +set [binary number v] to [] +repeat until <(decimal number) = [1]> + set [remainder v] to ((decimal number) mod (2)) + set [decimal number v] to ([floor v] of ((decimal number) / (2))) + set [binary number v] to (join (remainder) (binary number)) +end +set [binary number v] to (join (decimal number) (binary number)) +say (join (join (join [The binary representation for the number ] (answer)) [ is ]) (binary number)) +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary/decimal-to-binary.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary/decimal-to-binary.md new file mode 100644 index 000000000..4358537d4 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary/decimal-to-binary.md @@ -0,0 +1,21 @@ +# Convert a positive decimal number to a binary number + +## Requirement: + +Write a program that asks the user to enter any decimal number as the input +and displays the binary cards representing that number using '1' for dots +showing and '0' for not showing as the output (this converts the decimal +number to binary). +Do this by working out the largest bit value needed, and then working down +through smaller bit values. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +|-------|-------------------------------------------------------| +| 31 | The binary representation for the number 31 is 11111 | +| 32 | The binary representation for the number 32 is 100000 | +| 1 | The binary representation for the number 1 is 1 | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary/scratch-expected.md new file mode 100644 index 000000000..1e163f318 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary/scratch-expected.md @@ -0,0 +1,45 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148424317/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Please enter a decimal number:] and wait + +say (join (join (join [The binary representation for the number ] (answer)) [ is ]) (binary number)) +``` + +```scratch:split:random +if <<(decimal number) > (bit value)> or <(decimal number) = (bit value)>> then +else +end + +repeat until <(decimal number) < (bit value)> +end + +repeat until <(bit value) = [1]> +end +``` + +```scratch:split:random +set [decimal number v] to (answer) + +set [bit value v] to [1] + +set [binary number v] to [] + +set [bit value v] to ((bit value) * (2)) + +set [bit value v] to ((bit value) / (2)) + +set [binary number v] to (join (binary number) [1]) + +set [decimal number v] to ((decimal number) - (bit value)) + +set [binary number v] to (join (binary number) [0]) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary/scratch-hints.md new file mode 100644 index 000000000..50a44aef1 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary/scratch-hints.md @@ -0,0 +1,23 @@ +- Make variables called: + + - “decimal number” and set its value to the input number given by the + end user. + - “bit value” and set its value to ‘1’. + Find the smallest bit value which is larger than the “decimal number” + by doubling value of “bit value” until it is bigger than the “decimal + number”. + - “binary number” is a string variable and stores the binary cards + needed (‘1’ for dots showing and ‘0’ for not showing). + +- Set the variable “bit value” to 1 and find the smallest “bit value” which + is larger than “decimal number” by multiplying “bit value” by 2 (Use the + `scratch:() * ()` block under “Operators” to multiply the “bit value” by + 2) until it is larger than “decimal number”. + You can do this by using a `scratch:repeat until <>` loop. +- Now divide the “bit value” by 2 and check if “decimal number” is greater + than or equal to “bit value”. + If it is, add ‘1’ to string variable “cards” and subtract “bit value” from + the “ decimal number”. + If not, add ‘0’ to string variable “cards”. + Repeat until “bit value” is equal to 1. + Display the value of “cards” as the output. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary/scratch-solution.md new file mode 100644 index 000000000..16222ef7f --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/decimal-to-binary/scratch-solution.md @@ -0,0 +1,20 @@ +```scratch +when green flag clicked +ask [Please enter a decimal number:] and wait +set [decimal number v] to (answer) +set [bit value v] to [1] +set [binary number v] to [] +repeat until <(decimal number) < (bit value)> + set [bit value v] to ((bit value) * (2)) +end +repeat until <(bit value) = [1]> + set [bit value v] to ((bit value) / (2)) + if <<(decimal number) > (bit value)> or <(decimal number) = (bit value)>> then + set [binary number v] to (join (binary number) [1]) + set [decimal number v] to ((decimal number) - (bit value)) + else + set [binary number v] to (join (binary number) [0]) + end +end +say (join (join (join [The binary representation for the number ] (answer)) [ is ]) (binary number)) +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-largest-to-1/dots-largest-to-1.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-largest-to-1/dots-largest-to-1.md new file mode 100644 index 000000000..6af6731f3 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-largest-to-1/dots-largest-to-1.md @@ -0,0 +1,17 @@ +# Display number of dots from the given largest card to 1 + +## Requirement: + +Write a program that takes the largest number of dots on a card as an input +and displays the bit values from largest to smallest as the output. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +|-------|-------------------------------------------| +| 32 | 32
16
8
4
2
1 | +| 128 | 128
64
32
16
8
4
2
1 | +| 1 | 1 | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-largest-to-1/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-largest-to-1/scratch-expected.md new file mode 100644 index 000000000..9f0c56072 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-largest-to-1/scratch-expected.md @@ -0,0 +1,28 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148424005/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Enter the largest number of dots on a card:] and wait + +repeat until <(number of dots) = [1]> +end +``` + +```scratch:split:random +set [number of dots v] to (answer) + +set [number of dots v] to ((number of dots) / (2)) +``` + +```scratch:split +say (number of dots) for (1) secs + +say (number of dots) for (1) secs +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-largest-to-1/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-largest-to-1/scratch-hints.md new file mode 100644 index 000000000..cee4d6823 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-largest-to-1/scratch-hints.md @@ -0,0 +1,10 @@ +- Make a variable called “number of dots” and set it to the largest number + of dots entered as the input. + Divide the value of “number of dots” by 2 and display the result as the + output until “number of dots” is equal to 1. +- Use the `scratch:() / ()` operation under “Operators” to divide the + value of your variable by 2. +- Use the `scratch:repeat until <>` block to run the blocks inside while the + condition is true. + In this challenge you need to repeat the blocks until number of dots is + equal to 1. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-largest-to-1/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-largest-to-1/scratch-solution.md new file mode 100644 index 000000000..7a942c6d3 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-largest-to-1/scratch-solution.md @@ -0,0 +1,10 @@ +```scratch +when green flag clicked +ask [Enter the largest number of dots on a card:] and wait +set [number of dots v] to (answer) +say (number of dots) for (1) secs +repeat until <(number of dots) = [1]> + set [number of dots v] to ((number of dots) / (2)) + say (number of dots) for (1) secs +end +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-right-left/dots-right-left.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-right-left/dots-right-left.md new file mode 100644 index 000000000..96fe4aa43 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-right-left/dots-right-left.md @@ -0,0 +1,17 @@ +# Display number of dots from right to left in one line + +## Requirement: + +Write a program to display numbers 1, 2, 4, 8,... (all in one line) for a +given number of cards as the input. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +|-------|------------------------------| +| 4 | 1, 2, 4, 8, | +| 8 | 1, 2, 4, 8, 16, 32, 64, 128, | +| 1 | 1, | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-right-left/extra-challenge.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-right-left/extra-challenge.md new file mode 100644 index 000000000..98bffa4e9 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-right-left/extra-challenge.md @@ -0,0 +1 @@ +Now try removing the last `,` from the end of your output. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-right-left/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-right-left/scratch-expected.md new file mode 100644 index 000000000..b3e603599 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-right-left/scratch-expected.md @@ -0,0 +1,30 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148423954/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +set [binary cards v] to [] + +set [number of dots v] to [1] + +set [number of cards v] to (answer) + +set [binary cards v] to (join (binary cards) (join (number of dots) [, ])) + +set [number of dots v] to ((number of dots) * (2)) +``` + +```scratch:split:random +when green flag clicked + +ask [How many cards would you like to display?] and wait + +repeat (number of cards) +end + +say (binary cards) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-right-left/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-right-left/scratch-hints.md new file mode 100644 index 000000000..7ff7345ed --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-right-left/scratch-hints.md @@ -0,0 +1,17 @@ +- Make variables: + + + - “number of cards” to store the number of cards to display entered by + the end user as the input (e.g. 4). + - “number of dots” to store the number of dots. + - “binary cards” which is a variable type string and stores what is + going to be displayed on the screen as the output (e.g. “1, 2, 4, 8,”). + +- Use the `scratch: (join [hello] [world])` block under “Operators” to combine + two strings. + You can use multiple join blocks inside each other if you need to combine + more than two strings. +- In Scratch, to concatenate data together you use the + `scratch: (join [hello] [world])` blocks. + This can be used to join text, variables and calculations. + Remember to check if the spaces are correct between the items being joined! diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-right-left/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-right-left/scratch-solution.md new file mode 100644 index 000000000..2746cbf8f --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/dots-right-left/scratch-solution.md @@ -0,0 +1,12 @@ +```scratch +when green flag clicked +set [binary cards v] to [] +set [number of dots v] to [1] +ask [How many cards would you like to display?] and wait +set [number of cards v] to (answer) +repeat (number of cards) + set [binary cards v] to (join (binary cards) (join (number of dots) [, ])) + set [number of dots v] to ((number of dots) * (2)) +end +say (binary cards) +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/left-right-given-largest/extra-challenge.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/left-right-given-largest/extra-challenge.md new file mode 100644 index 000000000..98bffa4e9 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/left-right-given-largest/extra-challenge.md @@ -0,0 +1 @@ +Now try removing the last `,` from the end of your output. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/left-right-given-largest/left-right-given-largest.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/left-right-given-largest/left-right-given-largest.md new file mode 100644 index 000000000..b3dcd9fc0 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/left-right-given-largest/left-right-given-largest.md @@ -0,0 +1,18 @@ +# Display number of dots left to right with a given largest card in one line + +## Requirement: + +Write a program that takes the largest number of dots on a card as an input +and displays the bit values from largest to smallest all in one line as the +output. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +|-------|-------------------------| +| 64 | 64, 32, 16, 8, 4, 2, 1, | +| 8 | 8, 4, 2, 1, | +| 1 | 1, | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/left-right-given-largest/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/left-right-given-largest/scratch-expected.md new file mode 100644 index 000000000..9e32d8cba --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/left-right-given-largest/scratch-expected.md @@ -0,0 +1,36 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148424023/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Enter the largest card number:] and wait + +say (binary cards) + +repeat until <(number of dots) = [1]> +end +``` + +```scratch:split:random +(join (answer) [, ]) + +(join (binary cards) (join (number of dots) [, ])) + +(number of dots) / (2) +``` + +```scratch:split:random +set [number of dots v] to () + +set [number of dots v] to (answer) + +set [binary cards v] to () + +set [binary cards v] to () +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/left-right-given-largest/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/left-right-given-largest/scratch-hints.md new file mode 100644 index 000000000..0d6f5c508 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/left-right-given-largest/scratch-hints.md @@ -0,0 +1,14 @@ +- Make a variable called “number of dots” and set it to the largest number + of dots entered as the input. + Repeat dividing the value of “number of dots” by 2 and add the result + each time to a string variable called “binary cards” until “number of + dots” is equal to 1. + Display the value of “binary cards” as the output. +- Use the `scratch:(join [hello] [world])` block under “Operators” to + combine two strings. + You can use multiple join blocks inside each other if you need to combine + more than two strings. +- In Scratch, to concatenate data together you use the + `scratch:(join [hello] [world])` blocks. + This can be used to join text, variables and calculations. + Remember to check if the spaces are correct between the items being joined! diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/left-right-given-largest/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/left-right-given-largest/scratch-solution.md new file mode 100644 index 000000000..bd9211564 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/left-right-given-largest/scratch-solution.md @@ -0,0 +1,11 @@ +```scratch +when green flag clicked +ask [Enter the largest card number:] and wait +set [number of dots v] to (answer) +set [binary cards v] to (join (answer) [, ]) +repeat until <(number of dots) = [1]> + set [number of dots v] to ((number of dots) / (2)) + set [binary cards v] to (join (binary cards) (join (number of dots) [, ])) +end +say (binary cards) +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-bits-representing-number/number-bits-representing-number.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-bits-representing-number/number-bits-representing-number.md new file mode 100644 index 000000000..3e3174fd2 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-bits-representing-number/number-bits-representing-number.md @@ -0,0 +1,23 @@ +# Display the number of bits needed to represent a number + +## Requirement: + +Write a program that asks the user to enter a number of dots (i.e. decimal +value) as the input and displays how many bits will be needed to represent +that number as the output. +For example, the number 15 can be represented in 4 bits, but 16 requires 5 bits. +You can work this out by starting with the number 1, and doubling it until it +gives you a value more than the number you need to represent, so this program +can be based on the earlier challenge to work out the number of dots on each card. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +|-------|--------| +| 5 | 3 bits | +| 31 | 5 bits | +| 32 | 6 bits | +| 1 | 1 bits | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-bits-representing-number/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-bits-representing-number/scratch-expected.md new file mode 100644 index 000000000..aabbeb54c --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-bits-representing-number/scratch-expected.md @@ -0,0 +1,30 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148424235/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Please enter the number of dots:] and wait + +change [bits v] by (1) + +repeat until <(total number of dots) < (bit value)> +end + +say (join (join (join [You will need ] (bits)) [ bits to store number ]) (total number of dots)) +``` + +```scratch:split:random +set [total number of dots v] to (answer) + +set [bits v] to [0] + +set [bit value v] to [1] + +set [bit value v] to ((bit value) * (2)) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-bits-representing-number/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-bits-representing-number/scratch-hints.md new file mode 100644 index 000000000..5b39d04cf --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-bits-representing-number/scratch-hints.md @@ -0,0 +1,30 @@ +- Make variables called: + + - "total number of dots" and set its value to the number entered by the + end user. + - “bit value” and set its value to ‘1’. + Double its value until it is bigger than the “total number of dots”. + - “bits” and set its value to 0. + Every time that you double the “bit value” you need to add a new bit + for storing the number (increase the value of “bits” by 1). + +- Use `scratch:repeat until <>` block to repeat the blocks until “total + number of dots” is greater than the “bit value”. + Stop the loop when you find the “bit value” which is larger than the + “total number of dots”. + Display the value of “bits” as the output. +- Set the variable “bit value” to ‘1’ and double it (Use the + `scratch: () * ()` operation under “Operators” to multiply the “bit value” + by 2) until it is bigger than the “total number of dots”. + Every time that you double the “bit value” you need to add a new bit for + storing the number. + You can do this by using the `scratch:change [] by ()` block to increase + the value of variable “bits” value by 1. + Use `scratch:repeat until <>` block to repeat these steps until condition + is true. + In this challenge you need to repeat the blocks until + “total number of dots” is greater than the “bit value”. + Stop the loop when you find the “bit value” is larger than the “total + number of dots”. +- Test your program with some values on the boundaries (for example number + 31 would need 5 bits and 32 needs 6 bits). diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-bits-representing-number/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-bits-representing-number/scratch-solution.md new file mode 100644 index 000000000..27c69434c --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-bits-representing-number/scratch-solution.md @@ -0,0 +1,12 @@ +```scratch +when green flag clicked +ask [Please enter the number of dots:] and wait +set [total number of dots v] to (answer) +set [bits v] to [0] +set [bit value v] to [1] +repeat until <(total number of dots) < (bit value)> + set [bit value v] to ((bit value) * (2)) + change [bits v] by (1) +end +say (join (join (join [You will need ] (bits)) [ bits to store number ]) (total number of dots)) +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-cards-display/number-cards-display.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-cards-display/number-cards-display.md new file mode 100644 index 000000000..7193c912d --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-cards-display/number-cards-display.md @@ -0,0 +1,17 @@ +# Display number of dots for a given number of cards + +## Requirement: + +Write a program to display numbers 1, 2, 4, 8,... (one at a time) for a given +number of cards entered as the input. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided. + +| Input | Output | +|-------|-------------------------------------------| +| 5 | 1
2
4
8
16 | +| 8 | 1
2
4
8
16
32
64
128 | +| 1 | 1 | diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-cards-display/scratch-expected.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-cards-display/scratch-expected.md new file mode 100644 index 000000000..c88853b4e --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-cards-display/scratch-expected.md @@ -0,0 +1,26 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148423938/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +set [number of dots v] to [1] + +set [number of dots v] to ((number of dots) * (2)) + +set [number of cards v] to (answer) +``` + +```scratch:split:random +when green flag clicked + +ask [How many cards would you like to display?] and wait + +repeat (number of cards) +end + +say (number of dots) for (1) secs +``` + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-cards-display/scratch-hints.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-cards-display/scratch-hints.md new file mode 100644 index 000000000..7c2dafc2d --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-cards-display/scratch-hints.md @@ -0,0 +1,15 @@ +- Make a variable called “number of cards” to store the number of cards to + display entered by the end user as the input (e.g. 5) and a variable + called “number of dots” to store the number of dots (e.g. 1, 2, 4, 8, 16) + where each number is calculated by multiplying the previous number by 2. + +- In this challenge, use the `scratch:ask [What's your name?] and wait` + block to receive an input from the user. + Replace the text “What’s your name?” with your own question. + The user input will be stored in a predefined variable called “answer” + after the check mark button is clicked or the “Enter” key is pressed. + You can view the value of “answer” by clicking the checkbox next to the + answer block. + Set the value of variable “number of cards” to “answer”. + Use a repeat loop, repeating the blocks inside (displaying the number of + dots) “number of cards” times. diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-cards-display/scratch-solution.md b/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-cards-display/scratch-solution.md new file mode 100644 index 000000000..f583db89d --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/number-cards-display/scratch-solution.md @@ -0,0 +1,10 @@ +```scratch +when green flag clicked +set [number of dots v] to [1] +ask [How many cards would you like to display?] and wait +set [number of cards v] to (answer) +repeat (number of cards) + say (number of dots) for (1) secs + set [number of dots v] to ((number of dots) * (2)) +end +``` diff --git a/csunplugged/topics/content/en/binary-numbers/programming-challenges/programming-challenges.yaml b/csunplugged/topics/content/en/binary-numbers/programming-challenges/programming-challenges.yaml new file mode 100644 index 000000000..c2efa8805 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/programming-challenges/programming-challenges.yaml @@ -0,0 +1,286 @@ +binary-numbers-no-calculations: + challenge-set-number: 1 + challenge-number: 1 + difficulty-level: 1 + programming-languages: + - python + - scratch + learning-outcomes: + - programming-explain-sequencing + - programming-explain-output + extra-challenge: extra-challenge.md + +binary-numbers-using-variable: + challenge-set-number: 1 + challenge-number: 2 + difficulty-level: 1 + programming-languages: + - python + - scratch + learning-outcomes: + - programming-explain-sequencing + - programming-describe-variables + extra-challenge: extra-challenge.md + +binary-numbers-using-variables-as-operator: + challenge-set-number: 1 + challenge-number: 3 + difficulty-level: 1 + programming-languages: + - python + - scratch + learning-outcomes: + - programming-explain-sequencing + - programming-outline-variable-updates + extra-challenge: extra-challenge.md + +binary-numbers-using-variable-operator-repeat: + challenge-set-number: 1 + challenge-number: 4 + difficulty-level: 1 + programming-languages: + - scratch + learning-outcomes: + - programming-outline-variable-updates + - programming-describe-variable-output + - programming-identify-loop + extra-challenge: extra-challenge.md + +binary-numbers-one-line: + challenge-set-number: 1 + challenge-number: 5 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-describe-variables + - programming-identify-loop + - programming-explain-join-concatenate + extra-challenge: extra-challenge.md + +number-cards-display: + challenge-set-number: 2 + challenge-number: 1 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-loop + +dots-right-left: + challenge-set-number: 2 + challenge-number: 2 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-explain-join-concatenate + extra-challenge: extra-challenge.md + +dots-largest-to-1: + challenge-set-number: 3 + challenge-number: 1 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-loop + +left-right-given-largest: + challenge-set-number: 3 + challenge-number: 2 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-loop + - programming-explain-join-concatenate + extra-challenge: extra-challenge.md + +count-5-bw-no-loop: + challenge-set-number: 4 + challenge-number: 1 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-if-statement + +count-5-bw-using-loop: + challenge-set-number: 4 + challenge-number: 2 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-if-statement + - programming-identify-loop + +count-5-bw-one-input-no-loop: + challenge-set-number: 4 + challenge-number: 3 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-if-statement + - programming-demonstrate-indexing + +count-5-bw-one-input-using-loop: + challenge-set-number: 4 + challenge-number: 4 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-if-statement + - programming-identify-loop + - programming-demonstrate-indexing + +count-bw-one-input-using-loop: + challenge-set-number: 4 + challenge-number: 5 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-if-statement + - programming-identify-loop + - programming-demonstrate-indexing + +cards-given-number: + challenge-set-number: 5 + challenge-number: 1 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-if-statement + +cards-given-number-one-line: + challenge-set-number: 5 + challenge-number: 2 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-if-statement + extra-challenge: extra-challenge.md + +number-bits-representing-number: + challenge-set-number: 6 + challenge-number: 1 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-loop + +binary-cards-representing-number: + challenge-set-number: 7 + challenge-number: 1 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-if-statement + - programming-identify-loop + +binary-cards-representing-any-number: + challenge-set-number: 7 + challenge-number: 2 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-if-statement + - programming-identify-loop + +decimal-to-binary: + challenge-set-number: 7 + challenge-number: 3 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-if-statement + - programming-identify-loop + +decimal-to-binary-alternative: + challenge-set-number: 7 + challenge-number: 4 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-if-statement + - programming-identify-loop + +binary-notes-representing-number: + challenge-set-number: 8 + challenge-number: 1 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-if-statement + - programming-identify-loop + +binary-notes-representing-any-number: + challenge-set-number: 8 + challenge-number: 2 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-if-statement + - programming-identify-loop + extra-challenge: extra-challenge.md + +binary-cards-as-dots: + challenge-set-number: 9 + challenge-number: 1 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-identify-if-statement + - programming-identify-loop diff --git a/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/codes-for-letters-using-binary-representation-ct-links.md b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/codes-for-letters-using-binary-representation-ct-links.md new file mode 100644 index 000000000..3d71c993e --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/codes-for-letters-using-binary-representation-ct-links.md @@ -0,0 +1,105 @@ +{panel type="ct-algorithm" title="Algorithmic thinking"} + +We used two algorithms in this lesson: one to convert a letter into a decimal number and then into a binary number, and vice versa. +These are algorithms because they are a step-by-step process that will always give the right solution for any input you give it as long as the process is followed exactly. + +Here’s an algorithm for converting a letter into a decimal number: + +Choose a letter to convert into a decimal number. Find the letter’s numerical position in the alphabet as follows: + +- Say “A” (the first letter in the alphabet) +- Say “1” (the first letter in our sequence of numbers) + - Repeat the following instructions until you come to the letter you are looking to convert + - Say the next letter in the alphabet + - Say the next number (counting up by 1) +- The number you just said is the decimal number that your letter converts too. + +For example, to convert the letter E, the algorithm would have you counting A,1; B, 2; C, 3; D, 4; E, 5. + +{image file-path="img/topics/binary_count_girl.png" alt="Girl thinking about the algorithm"} + +(A more efficient algorithm would have a table to look up, like the one created at the start of the activity, and most programming languages can convert directly from characters to numbers, with the notable exception of Scratch, which needs to use the above algorithm.) + +The next algorithm is the same algorithm we used in lesson 1, which we use to convert a decimal number to a binary number: + +- Find out the number of dots that is to be displayed. (We'll refer to this as the "number of dots remaining", which initially is the total number to be displayed.) +- For each card, from the left to the right (i.e. 16, 8, 4, 2, then 1): + - If the number of dots on the card is more than the number of dots remaining: + - Hide the card + - Otherwise: + - Show the card + - Subtract the number of dots on the card from the number of dots remaining + +#### Examples of what you could look for: + +Have students create instructions for, or demonstrate, converting a letter into a decimal number (with or without the table), and then convert a decimal number into binary; are they able to show a systematic solution? +Can they explain what they are doing at each step and why? + +{panel end} + +{panel type="ct-abstraction" title="Abstraction"} + +This activity is particularly relevant to abstraction, since we are representing written text with a simple number, and the number can be represented using binary digits, which, as we know from lesson 1, are an abstraction of the physical electronics and circuits inside a computer. +We could also expand our abstraction because we could use any two symbols other than 0s and 1s to represent our message (although while students are first learning this we recommend sticking with 1s and 0s). +For example, you could represent your message by flashing a torch on and off (this gives an idea of how information might be sent over a fibre-optic cable!), or drawing a line of squares and triangles on the whiteboard. + +{image file-path="img/topics/binary_torch.png" alt="Flashlight"} + +Abstraction helps us simplify things because we can ignore the details we don’t currently need to know. +Binary number representation is an abstraction that hides the complexity of the electronics and hardware inside a computer that stores data. +Letters are an abstraction that a human can make sense of quickly; talking about the letter H is generally more meaningful than calling it "the 10th letter of the alphabet", and when we are reading or speaking we don’t need to know it is the 10th letter anyway. + +#### Examples of what you could look for: + +When you use a different representation for binary, such as turning the torch on and off, who are the students who quickly see that this is equivalent to when they previously used 0s and 1s? +They will probably feel comfortable working with this new representation quickly, and other students may be very confused by this change. +Look for students who then decide to create their own representations of binary numbers. + +{panel end} + +{panel type="ct-decomposition" title="Decomposition"} + +The core example of decomposition in this activity is understanding that in computing we have to break down all information into tiny chunks so that computers can store and send this data as bits and bytes. Everything we store inside a computer and see appear on the screen has to have been, in some way, broken down into binary digits. + +In this lesson students have performed several steps of decomposition as they have taken the task of encoding a message and broken it down into simple steps. To write a message in binary we have to first look at the message one letter at a time and convert each of these, one-by-one, into decimal numbers, and then convert each of these numbers, one-by-one, into binary numbers. Students perform these same steps in reverse to convert the message back to text. + +#### Examples of what you could look for: + +Can students explain why it is important that we can use binary to represent letters? Ask them why it is useful each separate letter into binary, rather than choosing a decimal and binary number for each different word. + +{panel end} + +{panel type="ct-pattern" title="Generalising and patterns"} + +Recognising patterns in the way the binary number system works helps give us a deeper understanding of the concepts involved, and assists us in generalising these concepts and patterns so that we can apply them to other problems. + +#### Examples of what you could look for: + +Have students decode a binary message from another student, by converting the binary numbers into decimal numbers, and then to text to view the message. Ask them what they would do if they wanted to include other characters in their message: what if we wanted upper and lower case letters? What if we want to use exclamation and question marks? +Observe which students see that we can simply generalise the method they are already using and can match other characters to bigger decimal numbers, e.g a-z can be 1-26, and A-Z can be 27-52. +If we can represent 32 different characters in binary when we use 5 bits for each character, then how many would we need for 64? Which students can see the pattern of binary and doubling in this situation, and see that we simply need to use 1 more bit to do this? + +{panel end} + +{panel type="ct-logic" title="Logic"} + +Logical thinking involves making decisions based on knowledge you have, and these decisions should be sensible and well thought out. If you memorise that the letter H is represented as binary 01010 it's not as useful as learning how to represent any character using the process described in this activity. If you can understand the logical steps we take as we convert a letter into a binary number, and how we can convert it back, then you will be able to represent any character as binary, and more importantly, you understand the process, since you're more likely to get a computer to do it for you rather than always do it manually. This is especially relevant if we want to represent a large number of characters. What if we wanted to represent every Chinese character? There are over 50,000 of them so trying to memorise them all would take a long time! +When we choose the decimal numbers to use for each of the letters we didn’t have to choose 1 to 26, we could have decided to start at 17 instead and go from 17 to 42, or we could have chosen completely random numbers! What if we decide that A = 82, B = 5, C = 42… Would this be a logical decision to make? 1 to 26 makes much more sense because it is much easier to describe and remember. + +#### Examples of what you could look for: + +Observe the systems students have created to translate their letters into binary and vice versa. What logic has been applied to these? Are they efficient systems? Can they explain what they are doing at each step? +Ask students why we are using the numbers 1 to 26 to represent our letters, or if they think there could be a better choice. Ask them how they would choose numbers for other characters, such as choosing a number to represent a space. Which ones give logical answers and can explain why their solution is a good choice? + +{panel end} + +{panel type="ct-evaluation" title="Evaluation"} + +An example of evaluation is working out how many different characters can be represented by a given number of bits e.g. 5 bits can represent 26 characters comfortably, but 16 bits are needed for a language with 50,000 characters. When thinking about how many bits to use to represent something Computer Scientists also have to think about are how much space this is going to take up on a computer (16-bit characters take up twice the space of 8-bit characters), and if we should have some extra bits in case we want to add more characters in the future. Evaluating the benefits and costs of using a certain number of bits is also an idea students can explore. + +#### Examples of what you could look for: + +Can a student work out how many bits are needed to represent the characters in a language with 100 characters? (7 bits are needed) +How about representing emojis, when you have about 4000 emojis available (12 bits will be needed for each one). + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/codes-for-letters-using-binary-representation.md b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/codes-for-letters-using-binary-representation.md new file mode 100644 index 000000000..9c210b6a8 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/codes-for-letters-using-binary-representation.md @@ -0,0 +1,143 @@ +# Codes for letters using binary representation + +## Key questions + +- How many different characters can you type on a computer? + (Discussion may start at the 26 letters of the English alphabet, and then + expand to other characters on the keyboard, including capital letters, + digits and punctuation. + Students may be aware that other languages can have thousands of + characters, and the range of characters is also expanding as emoticons + are invented!) + +## Lesson starter + +{panel type="general" title="Notes on resources"} + +There is also an online interactive version of the binary cards [here](http://www.csfieldguide.org.nz/en/interactives/binary-cards/index.html?digits=8), from the [Computer Science Field Guide](http://www.csfieldguide.org.nz/), but it is preferable to work with physical cards. + +{panel end} + +Discuss how you would communicate a letter of the alphabet to someone if all +you could do is say a number between 0 and 26. +*(Students will usually suggest using a code of 1 or a, 2 for b, and so on)*. + +Work out and write down the binary numbers using 5 bits from 0 to 26 on the +Binary to Alphabet resource, then add the letters of the alphabet. + +{image file-path="img/topics/col_binary_robot_boy_convo.png" alt="Cartoon boy talking to robot"} + +{panel type="teaching" title="Teaching observations"} + +- Check their binary code for three is 00011 as students commonly write + 00100 as they anticipate the pattern without necessarily checking it is + correct. +- Check they are writing the binary code in the correct order with the + least significant value on the right - for example some will start with + one as 10000 instead of 00001. +- Identify the students who are demonstrating one or all of these + computational thinking attributes; being very systematic, have everything + lined up exactly, are the first to recognise the pattern and don’t need + the cards any more. +- Check that all students can describe back to you how to calculate the + number they are up to. + This will identify those who are guessing the pattern. +- Note that if your local alphabet is slightly different (e.g. has + diacritics, macrons etc.) then you may wish to adapt the code to + match the common characters; this issue is also considered below. + +{panel end} + +## Lesson activities + +Using the table that students have created above, give them a message to +decode, such as your name or the name of a book +author (e.g. 00001 00010 00010 11001 for ABBY). + +Now get students to write and communicate their own messages. +Remind them that they can write the zeroes and ones using any symbols, such +as ticks and crosses. + +Consider unusual representations; for example, each bit could be communicated +with a sound that is either high pitched or low pitched. +Or the 5-bit number could be represented by holding up the five fingers on +one hand, one finger corresponding to each bit. + +## Adding more characters + +Some languages have slightly more or fewer characters, which might include +those with diacritic marks. +If students consider an alphabet with more than 32 characters, then 5 bits +won't be sufficient. +Also, students may have realised that a code is needed for a space +(0 is a good choice for that), so 5 bits only covers 31 alphabet +characters. + +Have the students design a system that can handle a few extra characters such +as diacritics. +(This can usually be done by allocating larger numbers, such as 27 to 31, to +the other characters). + +A typical English language computer keyboard has about 100 characters +(which includes capital and lowercase letters, punctuation, digits, and +special symbols). +How many bits are needed to give a unique number to every character +on the keyboard? +(Typically 7 bits will been enough since it provides 128 different codes). + +Now have students consider larger alphabets. +How many bits are needed if you want a number for each of 50,000 Chinese +characters? +(16 bits allows for up to 65,536 different representations). + +{panel type="teaching" title="Teaching observations"} + +It may be a surprise that only 16 bits is needed for tens of thousands of +characters. +This is because each bit doubles the range, so you don't need to add many +bits to cover a large alphabet. +This is an important property of binary representation that students should +become familiar with. + +{panel end} + +{panel type="math" title="Mathematical links"} + +The rapid increase in the number of different values that can be represented +as bits are added is *exponential* growth i.e. it doubles with each extra bit. +After doubling 16 times we can represent 65,536 different values, and 20 bits +can represent over a million different values. +Exponential growth is sometimes illustrated with folding paper in half, and +half again. +After these two folds, it is 4 sheets thick, and one more fold is 8 sheets +thick. +16 folds will be 65,536 sheets thick! +In fact, around 6 or 7 folds is already impossibly thick, even with a large +sheet of paper. + +{panel end} + +{panel type="teaching" title="Teaching observations"} + +Using a 5-bit code for an alphabet goes back to at least 1870 (the "Baudot" +code); many different number to letter correspondences have been used over the +years to represent alphabets, but one that was common for some time is "ASCII", +which used 7 bits and therefore could represent over 100 different characters. +These days "Unicode" is common, which can represent over 100,000 different +characters. +Nevertheless, each of these codes, including Unicode, still contain elements +of the simple code used in this lesson (A is 1, B is 2...). +For example, the ASCII code for "A" is 65 and "B" is 66 etc.; if you work +out the binary representations of these, and just keep the right-most 5 bits, +you've got the code that we've been using above. + +{panel end} + +## Lesson reflection + +- What are some reasons why we don’t use the binary number system as the + language for our written language? +- How do you think ancient Egyptians would have converted their + hieroglyphics to binary? +- What did you find challenging during this lesson? +- How did you overcome these challenges? diff --git a/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/how-binary-digits-work-ct-links.md b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/how-binary-digits-work-ct-links.md new file mode 100644 index 000000000..433a77f45 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/how-binary-digits-work-ct-links.md @@ -0,0 +1,138 @@ +{panel type="ct-algorithm" title="Algorithmic thinking"} + +We used an algorithm in this lesson to convert a decimal number to a binary one. +This is an algorithm because it is a step-by-step process that will always give the right solution for any input you give it as long as the process is followed exactly. + +Here’s an algorithm for working out which dot cards should be showing, written in text: + + - Find out the number of dots that is to be displayed. (We'll refer to this as the "number of dots remaining", which initially is the total number to be displayed.) + - For each card, from the left to the right (i.e. 16, 8, 4, 2 then 1): + - If the number of dots on the card is more than the number of dots remaining: + - Hide the card + - Otherwise: + - Show the card + - Subtract the number of dots on the card from the number of dots remaining + +Note that this algorithm (working from right to left) works very well with the cards, but if you look up computer programs for doing this, you may encounter a different one that works from right to left. It's usual to have multiple algorithms that achieve the same thing. + +#### Examples of what you could look for: + +Which students are methodical when they convert between decimal and binary? +Which ones start with the leftmost card and move one card at a time to the right, rather than choosing cards at random and flipping them on and off until they get the right number? + +{panel end} + +{panel type="ct-abstraction" title="Abstraction"} + +Binary number representation (just using 0 and 1) is an abstraction that hides the complexity of the electronics and hardware inside a computer that store data. Abstraction helps us simplify things because we can ignore the details we don’t currently need to know. + +In this case the details we can ignore include: Computers use physical devices like electric circuits and voltages in circuits to store and move data, and there are many complex physics and mathematical theories that make this work. + +We don’t need to understand how these circuits work to use data and represent things using binary. +Using binary is an Abstraction of these circuits and allows us to represent numbers as being made out of bits (0s and 1s), to understand data and work out problems without having to think about what is happening ‘underneath the hood’ of the computer. + +Another use of abstraction is considering what is needed to represent any given digit in binary. +The answer is all you need are two different things. +These things can be anything! Two different colours, two different animals, two different symbols etc. As long as there are two of them, and they are different, you can use these to represent any number, using binary, the same way a computer uses electricity to represent data. + +We can use binary digits to represent any type of data stored on a computer. +When we represent other forms of data (such as letters, images, and sound) we also use abstraction because we hide the details of all the binary numbers underneath and just look at the whole piece of data. +All forms of data end up being represented as numbers (which in turn are really just combinations of bits) - for text we have a number for each letter, for images we use a number for each colour, and so on. We are using multiple layers of abstraction! +For example, a familiar form of abstraction is that the month "October" could be represented by the number ten, which in turn is represented by the bits 01010, and if these are stored as voltages in computer memory, it is ultimately "low, high, low, high, low" for the voltages. + +#### Examples of what you could look for: + +Who are the students that demonstrate converting and representing binary numbers using things other than “1’s and 0’s”, “black and white”, and “off and on” (for example using :) and :(, or using people standing up or sitting down). +If you are able to interchange terms like "black" and "white" with 0 and 1 without students being concerned about the difference, they are exercising abstraction. + +{panel end} + +{panel type="ct-decomposition" title="Decomposition"} + +An example of decomposition is breaking the conversion of the number to binary into one bit at a time. The questions "Should this be 1 or 0" for each of the dot cards is decomposing the problem to a series of questions. + +#### Examples of what you could look for: + +Which students recognise that it is important to start with the leftmost card and only consider one bit at a time? Which students focus on each individual bit at a time, rather than being overwhelmed by trying to work them all out in one go? + +{panel end} + +{panel type="ct-pattern" title="Generalising and patterns"} + +Recognising patterns in the way the binary number system works helps give us a deeper understanding of the concepts involved, and assists us in generalising these concepts and patterns so that we can apply them to other problems. + +At a simple level, we started with the numbers 1, 2, and 4, and students generalised that to doubling values. The exercise used 5-bit numbers, but students should be able to generalise that to 8-bit numbers, or larger. + +The algorithm for converting a decimal number to a binary one follows a pattern that can be generalised to solve the problem of giving change when someone pays by cash. For binary numbers you start with the largest bit always turn a bit on if you need it, just like when you’re giving change you start with the largest denomination and then always take a coin (or note) whenever you need it. +Jargon note: This is called a greedy algorithm - it takes as much as it can each time! + +{panel type="math" title="Mathematical links"} + +Ask students what is special about the decimal to binary conversion, in contrast with the general change giving algorithm, and have them observe that in the general case you may need to give more than one coin of the same denomination, whereas in the binary conversion there is always one (or none) of each. + +{panel end} + +When counting upwards in binary, there is a pattern for how often particular cards flip. +The 1st bit (with 1 dot) turns over every time, the 2nd (with 2 dots) turns for every second number, the 3rd (with 4 dots) turns for every 4th... Is there a pattern like this when we count in decimal numbers? + +{image file-path="img/topics/col_binary_counting_pattern.png" alt="Binary counting pattern"} + +If you have 5 of the cards and all are visible, you will have the number 31, which is 1 less that the value of the next card, 32. Is this pattern always true? + +The amount of numbers you can represent with a certain number of bits is the same as the value of the next bit that can be added. +For example, using 4 cards (1, 2, 4, 8) you can represent 16 different numbers (0-15), and the next card in the sequence is the number 16. +Each time we add the next card we also double the amount of different numbers we can represent. + +Working with these patterns is valuable for working out the relationship between the number of bits being used and the power of what they can represent. + +Explain one or more of the following patterns: + +- That with a certain number of cards you can make the same amount of different numbers as the number of dots that would be on the next card to be added on the left (remember that 0 is a number). +- When you are counting upwards: the first card (1 dot) turns over every time, the second card (2 dots) turns every two times, the third (4 dots), every four times, and the fourth (8 dots), every eight times... +- That when all the cards you have are visible it will add up to the next binary card number minus 1. + +#### Examples of what you could look for: + +Which students recognised quickly that each card was doubling the number of dots? Can students see the similarities between this and multiplying place values by 10 when they are using the decimal system? + +Which students easily understand the patterns of cards flipping when counting with binary numbers? + +{panel end} + +{panel type="ct-logic" title="Logic"} + +Logical thinking means using rules you already know and using logic to deduce more rules and information from these. +Once we know what number each of the binary cards represents then we can use this knowledge to figure out how to represent other numbers with the cards. +If you memorise how to represent the numbers we can make with 5 cards, does that mean you understand how to represent any number with any number of bits? +It doesn’t, but you can understand how to do that if you understand the logic behind how these numbers with the 5 cards are made. + +A good example of logical thinking in binary numbers is the reasoning for why each bit "has to" have a particular value (e.g. it has to be 1, or it has to be 0) to represent a given number. +This in turn leads to understanding that there is only one representation for each number. + +#### Examples of what you could look for: + +Do students explicitly explain that the right-most bit needs to be a one because it is the only odd number and therefore is needed so that we are able to make any, and all, odd numbers? Without it we could only make even numbers. + +Are students able to explain that each card "has to" be up the way it is for a given number e.g. the 16-dot card is needed for the number 19 because without it you only have 15 dots remaining to its right (not enough); but the 16 card isn't needed for the number 9 because it would give too many dots? + +{panel end} + +{panel type="ct-evaluation" title="Evaluation"} + +An example of evaluation is working out how many different values can be represented by a given number of bits (e.g. 5 bits can represent 32 different values), and vice versa (to represent 1000 different values, you need at least 10 bits). + +#### Examples of what you could look for: + +Can a student work out the range possible with 4 bits? (16) + +6 bits? (64) + +8 bits? (256) + +If we add one more bit to a representation, how much does that increase the range? (it doubles it) + +If we add two more bits to a representation, how much does that increase the range? (it is four times as much) + +How many bits do we need to represent 1000 different values? (10 is sufficient) + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/how-binary-digits-work-junior-ct-links.md b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/how-binary-digits-work-junior-ct-links.md new file mode 100644 index 000000000..2efaa34c7 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/how-binary-digits-work-junior-ct-links.md @@ -0,0 +1,134 @@ +{panel type="ct-algorithm" title="Algorithmic thinking"} + +We used an algorithm in this lesson to convert a decimal number to a binary one. +This is an algorithm because it is a step-by-step process that will always give the right solution for any input you give it as long as the process is followed exactly. + +Here’s an algorithm for working out which dot cards should be showing, written in text: + +- Find out the number of dots that is to be displayed. + (We'll refer to this as the "number of dots remaining", which initially is the total number to be displayed.) + +- For each card, from the left to the right (i.e. 8, 4, 2 then 1): + + - If the number of dots on the card is more than the number of dots remaining: + + - Hide the card + + - Otherwise: + + - Show the card + + - Subtract the number of dots on the card from the number of dots remaining + +#### Examples of what you could look for: + +Which students are methodical when they convert between decimal and binary? +Which ones start with the leftmost card and move one card at a time to the right, rather than choosing cards at random and flipping them on and off until they get the right number? + +{panel end} + +{panel type="ct-abstraction" title="Abstraction"} + +Abstraction and abstract thinking is generally difficult for young students so only a small amount of this section is likely to be applicable for them. +We have included the information here however because it is useful background knowledge for teaching this topic. + +Abstraction helps us simplify things because we can ignore the details we don’t currently need to know. +Binary number representation is an abstraction that hides the complexity of the electronics and hardware inside a computer that store data. + +In this case the details we can ignore include: Computers use physical devices like electric circuits and voltages in circuits to store and move data, and there are many complex physics and mathematical theories that make this work. + +We don’t need to understand how these circuits work because we can use the abstraction of binary, as numbers made up of bits (0s and 1s), to understand data and work out problems, without having to think about what is happening ‘underneath the hood’ of the computer. + +Another use of abstraction is considering what is needed to represent any given digit in binary. The answer is all you need are two different things. +These things can be anything! Two different colours, two animals, two symbols etc. +As long as there are two of them, and they are different, you can use these to represent any number, using binary, the same way a computer uses electricity to represent data. + +#### Examples of what you could look for: + +Who are the students that can demonstrate converting and representing binary numbers using things other than “1’s and 0’s”, “black and white”, and “off and on” (for example using :] and :[, or using people standing up or sitting down). +If you are able to interchange terms like "black" and "white" with 0 and 1 without students being concerned about the difference, they are exercising abstraction. + +{panel end} + +{panel type="ct-decomposition" title="Decomposition"} + +An example of decomposition is breaking the conversion of the number to binary into one bit at a time. +The questions "Should this be 1 or 0" for each of the dot cards is decomposing the problem to a series of questions. + +#### Examples of what you could look for: + +Which students recognise that it is important to start with the leftmost card and only consider one bit at a time? +Which students focus on each individual bit at a time, rather than being overwhelmed by trying to work them all out in one go? + +{panel end} + +{panel type="ct-pattern" title="Generalising and patterns"} + +Recognising patterns in the way the binary number system works helps give us a deeper understanding of the concepts involved, and assists us in generalising these concepts and patterns so that we can apply them to other problems. +Generalising these patterns may be more difficult for junior students, but recognising the patterns is a good exercise. + +At a simple level, we started with the numbers 1, 2, and 4, and students generalised that to doubling values. +In these exercises we converted to 4-bit numbers, but students (who are able to count high enough) may be able to generalise that to more 8-bit numbers, or larger. + +The algorithm for converting a decimal number to a binary one follows a pattern that can be generalised to solve the problem of giving change when someone pays by cash. +For binary numbers you start with the largest bit and turn it on if it is needed or off if it is not, just like when you’re giving change you start with the largest denomination and then always take a coin (or note) whenever you need it. +Jargon note: This is called a greedy algorithm. + +{panel type="math" title="Mathematical links"} + +Ask students what is special about the decimal to binary conversion, in contrast with the general change giving algorithm, and have them observe that in the general case, you may need to give more than one coin of the same denomination, whereas in the binary conversion there is always one (or none) of each. + +{panel end} + +When counting upwards in binary, there is a pattern for how often particular cards flip. +The first bit (with 1 dot) turns over every time we count up by one, the 2nd (with 2 dots) turns for every second number, the 3rd (with 4 dots) turns for every fourth… Is there a pattern like this when we count in decimal numbers? + +{image file-path="img/topics/col_binary_counting_pattern.png" alt="Binary counting pattern"} + +If you have 4 of the cards and all are visible, you will have the number 15, which is 1 less than the value of the next card, 16. Is this pattern always true? + +The amount of numbers you can represent with a certain number of bits is the same as the value of the next bit that can be added. +For example, using 4 cards (1, 2, 4, 8) you can represent 16 different numbers (0-15), and the next card in the sequence is the number 16. +Each time we add the next card we also double the amount of different numbers we can represent. + +Working with these patterns is valuable for working out the relationship between the number of bits being used and the power of what they can represent. + +#### Examples of what you could look for: + +Which students recognised quickly that each card was doubling the number of dots? +Which students easily understand the patterns of cards flipping when counting with binary numbers? + +{panel end} + +{panel type="ct-logic" title="Logic"} + +Logical thinking means using rules you already know and using logic to deduce more rules and information from these. +Once we know what number each of the binary cards represents then we can use this knowledge to figure out how to represent other numbers with the cards. +If you memorise how to represent the numbers we can make with 4 cards, does that mean you understand how to represent any number with any number of bits? +It doesn’t, but you can understand how to do that if you understand the logic behind how these numbers with the 4 cards are made. + +A good example of logical thinking in binary numbers is the reasoning for why each bit "has to" have a particular value (e.g. it has to be on, or it has to be off) to represent a given number. +This in turn leads to an argument that there is only one representation for each number. + +#### Examples of what you could look for: + +Do students explicitly explain that the first bit needs to be a one because it is the only odd number and therefore is needed so that we are able to make any, and all, odd numbers? Without it we could only make even numbers. +Are students able to explain that each card "has to" be up the way it is for a given number e.g. the 8-dot card is needed for the number 9 because without it you only have 7 dots remaining (not enough); but it's not needed for the number 6 because it would give too many dots? + +{panel end} + +{panel type="ct-evaluation" title="Evaluation"} + +An example of evaluation is working out how many different values can be represented by a given number of bits (e.g. 4 bits can represent 16 different values), and vice versa (to represent 1000 different values, you need at least 10 bits). + +#### Examples of what you could look for: + +Can a student work out the range possible with 2 bits? (4) + +3 bits? (8) + +4 bits? (16) + +If we add one more bit to a representation, how much does that increase the range? (it doubles it) + +{panel end} \ No newline at end of file diff --git a/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/how-binary-digits-work-junior.md b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/how-binary-digits-work-junior.md new file mode 100644 index 000000000..df9ce4be4 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/how-binary-digits-work-junior.md @@ -0,0 +1,241 @@ +# How binary digits work + +## Note from the authors + +We’ve noticed that when we teach the binary number system to students ages 5 - 7 we are focusing on number knowledge and number identification rather than how the binary number system works. We also support students to learn to count by one to one matching, because they are counting the dots. Students are motivated to learn because they are learning how computers store information. Students may ask you questions and be excited to explore the concepts outlined in this lesson further. We’ve added a lot of information into this lesson, however, it is not our intention that you will teach and cover all the concepts, but that you have at your fingertips the information you need when your students express an interest in learning more. + +## Key question + +- How do you think a digital device stores information? + +Accept and record all answers to revisit at the end of the lesson. + +## Lesson starter + +{panel type="general" title="Notes on resources"} + +There is also an online interactive version of the binary cards available (4 card version, corresponding to this activity, or 5 card version if students are comfortable with numbers up to 31), from the [Computer Science Field Guide](http://www.csfieldguide.org.nz/en/interactives/binary-cards/index.html?digits=8). However, we strongly recommend using physical cards to start with. + +{panel end} + +Did you know that right in the inside of any computer there are billions (that’s a really really big number) of little tiny things that can be turned on or off, like a light switch, and that when have lots of these things together they can display a number or a letter or a movie or make up your favourite game on your device? +So let’s look at how they work. +Now we need to pretend that we are so incredibly small that we are now inside a computer and we are making the computer show a number. +Are you ready? + +First of all here’s a card that is the tiny thing that can be turned on or off. +The technical word for that is a “bit”. + +1. Hold the 4 cards (1, 2, 4, and 8 dots), but don't let students see the dots. + Ask for 4 students to volunteer to be “bits”, and have them stand in a line in front of the class. + +2. Hand out the 1-dot card to the person on the right. + Explain that they are one "bit" (binary digit), and can be on or off, black or white, 0 or 1 dots. + The only rule is that their card is either completely visible, or not visible (i.e. flipped over). + Hand out the second card so that the first card is on the far right. + Point out that this card has either 2 dots, or none. + + {image file-path="img/topics/col_binary_4_kids_2_cards.png" alt="2 kids holding binary cards"} + +3. Ask the class what the number of dots on the next card will be. + Get them to explain why they think that. + + {panel type="teaching" title="Teaching observations"} + + Students will usually suggest it should be three. + If they suggest 4, they may have done the activity before (or have seen the cards you are holding!), or they may have very good pattern recognition skills! + If they suggest the wrong number, don't correct them, but continue without comment, so that they can construct the rule for themselves. + + {panel end} + +4. Silently give out the 4-dot card, and let them try to see the pattern. + This will be dependent on their level of number knowledge. + Mention that each number is doubling (or that if you had two of a card, that would give you the card beside it), and move onto the next step. + + {image file-path="img/topics/col_binary_4_kids_3_cards.png" alt="3 kids holding binary cards"} + + {panel type="teaching" title="Teaching observations"} + + Usually some students will point out that you've missed out the three, but simply indicate that you haven't made a mistake, something is happening to the numbers and we will be able to make three by using the cards. + + {panel end} + +5. Ask what the next card is, and why. + + {panel type="teaching" title="Teaching observations"} + + At this point it is common for students to guess that it is 6 (since it follow the numbers 2 and 4). + However, if you let them think about it a little more, some will usually come up with 8, and those students should be able to convince the others that they are correct. + There are several ways a student could explain this e.g. that each card is double the previous one, or that if you take two of a card, you get the next one. Some may recite the pattern 1+1=2, 2+2=4, 4+4=8. + + If your class are learning to count then show all the cards and count how many dots are on each one. + Look for the patterns of doubling and count the dots to prove this. + + {panel end} + +6. Show the 4th card and hand it out: + + {panel type="teaching" title="Teaching observations"} + + Background information for those who are curious! + If we kept handing out cards then once we got to 8 cards we would have 256 dots in total if we added up all the cards. + This is 8 bits, which is commonly referred to as a byte. + It may be distracting to bring this up at this point, but some students may already be familiar with the idea that 8 bits is a byte, and make that observation. + However, in the meantime, we'll work with a 4-bit representation, which isn't as useful as a whole byte, but a good size for teaching younger students. + 4 bits is actually called a nibble (sometimes spelt nybble)! + This is a fun extra piece of information for interested students. + + A common mistake is to hand out the cards from left to right, but it's convention in number representation that the least significant value is on the right, and this is an important idea for students to take away from this activity. + + {panel end} + +## Lesson activities + +1. Tell the students that the rule is that a card either has the dots showing, or hiding. + If we can turn cards on (showing) and off (hiding) by showing the front and back of the card, how would we show 3 dots? + Begin by asking: how many dots are on the left-most card? + Count together that there are 8 dots. + Let’s look at the number line. + Is 8 bigger than 3? + Let’s hide it because it’s too big. + Now let’s look at the next card, how many dots can we see? Let’s count them. + There are 4, is 4 bigger than 3? + Yes, so we need to hide the card. + If we didn’t hide the 4 card what would happen? + (There would be too many dots). + How many dots are on the next card? Let’s count them. + There are 2 dots. + Let’s look at the number 3, (show it with materials and show that 2 fits into 3, with how many left over?) + We need one more dot to make the number 3. + So we will leave that card visible. + + Without being given any rules other than each card being visible or not, students will usually come up with the following representation. + + {image file-path="img/topics/binary_cards_equals_three.png" alt="Diagram showing that 2 binary cards make the number 3"} + + {panel type="math" title="Mathematical links"} + + At a junior level we are focusing on using this counting system to represent numbers and making the association that with these “bits” you can make any number. + We’re not going to focus on further knowledge about number base systems. + The information below is extra information for you. + + For teachers: Base 10 (our normal counting system) has 10 digits, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. + When we count in base 10, we count from 0 to 9 and then run out of digits. + So we need to add another column; we put a 1 in that column and start counting again from 0. + This makes the number 10, we then repeat that process until the tens column is 9 and the ones column is 9 (making 99); from there we then add another column. + Hence we have the familiar place value system that can be shown something like this: + + 100,000s | 10,000s | 1,000s | 100s |10s | 1 + **Note: Use the appropriate place value example based on what you have already taught in your class; this is an extended example.** + + Base 2 (binary) follows the same logic, except it moves a lot quicker to the “next” place value, because there are only two digits, 0 and 1. + The binary place values look like this: + + 32 | 16 | 8 | 4 | 2 | 1 | + + Sometimes students confuse the order of digits in a binary representation. + To support students to understand the correct ordering of binary digits, ask the question: If I was going to give you $435.00 which number are you most interested in? Is it the 4 or the 5? Why is that? + It’s the same for binary code, the lowest value (least significant digit) is on the far right, whereas the most significant digit is on the far left. + + Base 16 is also commonly used on computers - it's called hexadecimal, and has 16 digits. + While this is far beyond the scope of this lesson, we wanted to point out that one hexadecimal digit is the equivalent of 4 binary digits (bits), because both can represent 16 different values (from 0 to 15). + So this exercise that the students are doing provides a great basis for later understanding another common representation that they will encounter on digital devices. + + {panel end} + +2. Now ask "How would you make the number 6?" (Again, start by asking if they want the 8 card, and so on from left to right). + +3. The process we have been following to make these numbers is an algorithm, which converts our normal counting numbers to a binary representation. + Together let’s think through the steps we followed to do this. + We'll do this by using the earlier example of representing the number 3 again. + + a. Start with all the numbers switched to on (all the dots visible). + You could start to introduce abstract representations, like varying the description of the cards (visible/hidden; dots/no-dots; white/black if the backing is black; yes/no; or on/off) + + {image file-path="img/topics/lightbulb_series_4_bulbs_4.png" alt="4 lightbulbs switched on"} + + b. Does 8 fit into 3? No - so turn it off + + {image file-path="img/topics/lightbulb_series_4_bulbs_3.png" alt="3 lightbulbs switched on"} + + c. Does 4 fit into 3? No - so turn it off + + {image file-path="img/topics/lightbulb_series_4_bulbs_2.png" alt="2 lightbulbs switched on"} + + d. Does 2 fit into 3? Yes - so keep it on. How many are left over? (1) + + e. Does 1 fit into 1? Yes - so keep it on. How many are left over? (none) + + {image file-path="img/topics/lightbulb_series_4_bulbs_2.png" alt="2 lightbulbs switched on"} + +## Applying what we have just learnt + +- Group students into pairs +- Give each pair a set of the smaller binary cards +- Have them practise the algorithm for numbers below 10. + +1. Explain to students that we're working with just two digits, so they are called binary digits. (You could explore the meaning of the "bi" prefix with words like bicycle, biennial, bilingual and bicultural.) + Binary digits are so common that we have a short name for them: write "binary digit" on a piece of paper, then rip off the "bi" at the start, and the "t" at the end, put it together and ask what the combined word ("bit") spells. + This is the short name for a binary digit, which is why we've been referring to the cards as bits; the 4 cards that they have are actually 4 bits. + +2. Now let’s count from the smallest number that we can make, up to the highest number + + a. What is the smallest number? (they may suggest 1, then realise that it’s actually 0). + +3. Get the number 0 displayed on the cards (i.e. no dots showing) + +4. Now count up 1, 2, 3, 4 …. (each pair should work out these numbers between them) + +5. Once they start to get into a routine, ask: how often are we seeing the 1-dot card? (every second time, every odd number) + + a. What other patterns are we seeing? (the 2-dot card flips on every second count, the 4-dot on every 4th and so on; the 8 dot card doesn't do much; this may be challenging for some students to recognise, and the main goal is that they are aware that the 1-dot card flips every time, and that every second number is an odd number). + +6. Continue until all the cards are switched to “on” and have counted to 15. + What happens next? (We have to add a new card.) + How many dots on it? (16) What do we have to do to the other 4 cards when we get to 16? (we have to turn them all off) + +7. Extension ideas ... + + a. So when I have two bits I can make a maximum of? (3) + + b. I add another bit and that has how many dots on it? (4) + + c. I turn off the first two bits to make 4 right? + + d. Now let’s turn on all three bits, so now we have how many? (7) + + e. I add another bit and that has how many dots on it? (8) + + f. Repeat until a pattern is recognised that the number on the next card is one more than the total number of dots on all the cards to the right (e.g. there are 7 dots on the 4, 2 and 1 cards, so the next card to the left is 8). + This makes it easy to work out the number if all the bits are switched on - double the left-hand card, and subtract 1. + + g. How many different numbers can I make with two bits? (4; often students will say 3 because they haven’t counted 0). + + h. Let’s add the next bit; how many different numbers can we make now? (8, again 7 will often be given as an answer first). + + i. Repeat until a pattern is recognised that each time we add another bit we can now represent twice as many numbers. + +{panel type="teaching" title="Teaching observations"} + +A concept that students may struggle with here is that the number of values is one more than the maximum value (e.g. from 0 to 7, there are 8 different numbers). +The same observation occurs with the number of digits in conventional decimal numbers; the largest digit is 9, but there are 10 possible digits (counting 0). +This is sometimes called the fencepost problem (the number of fence posts is one more than the number of gaps between them), and it comes up a lot in computing and in maths. + +{panel end} + +## Lesson reflection + +- Would this activity work if we used white and cream cards? Why? Why not? + + - In principle you could use these, but it wouldn’t be a good idea. + We are looking for the answer that they are not contrasting colours, therefore it would be difficult to see if it is actually on or off. + Computers are easier to build and break less often when we just use two contrasting values. + +- What are some other ways we could physically show that each of the bits are on or off? + + - Ideas could include holding the cards up high, or down low; simply holding up a hand; sitting down or standing up; or using a different representation such as lights that are on or off. + +- What else could we use to represent two opposites, like on and off, in writing? + + - Perhaps a cross or tick; happy or sad face; or any other pair of symbols. + diff --git a/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/how-binary-digits-work.md b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/how-binary-digits-work.md new file mode 100644 index 000000000..656c748c4 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/how-binary-digits-work.md @@ -0,0 +1,302 @@ +# How binary digits work + +## Note from the authors + +We’ve noticed that once students understand how the binary number system works, they have many questions and are excited to explore the concepts outlined in this lesson further. +We’ve added a lot of information into this lesson, however, it is not our intention that you will teach and cover all the concepts, but that you have at your fingertips the information you need when your students express an interest in learning more. + +## Key questions + +- What different number systems do we know about? (Answers might include: + Roman Numerals; Tally marks; Number bases like binary, octal and + hexadecimal; Language based systems like Chinese or Ancient Egyptian.) +- Why do we normally use 10 digits? (Probably because we have 10 + fingers, plus it's a fairly efficient way to write things compared with, + say, tally marks.) +- Why do we have different number systems? (They are convenient for + different things e.g. tally marks are easy if you are counting; Roman + numerals can be useful for making a number look more mysterious or harder + to read.) + +## lesson starter + +{panel type="general" title="Notes on resources"} + +There is also an online interactive version of the binary cards [here](http://www.csfieldguide.org.nz/en/interactives/binary-cards/index.html?digits=8), from the [Computer Science Field Guide](http://www.csfieldguide.org.nz/), but it is preferable to work with physical cards. + +{panel end} + +1. Hold the first 5 cards (1, 2, 4, 8 and 16 dots), but don't let students + see the dots. + Ask for 5 students to volunteer to be “bits”, and have them stand in a + line in front of the class. + +2. Hand out the 1-dot card to the person on the right. + Explain that they are one "bit" (binary digit), and can be on or off, + black or white, 0 or 1 dots. + The only rule is that their card is either completely visible, or not + visible (i.e. flipped over). + Hand out the second card to the second person from the right. + Point out that this card has either 2 dots (visible), or none + (upside down). + + {image file-path="img/topics/col_binary_2cards.png" alt="2 kids holding binary cards"} + +3. Ask the class what the number of dots on the next card will be. + Get them to explain why they think that. + + {panel type="teaching" title="Teaching observations"} + + Students will usually suggest it should be three. + If they suggest 4, they have probably done the activity before (or + have seen the cards you are holding!) + If they suggest the wrong number, don't correct them, but continue + without comment, so that they can construct the rule for themselves. + + {panel end} + +4. Silently give out the four-dot card, and let them try to see the pattern. + + {image file-path="img/topics/col_binary_3cards.png" alt="3 kids holding binary cards"} + + {panel type="teaching" title="Teaching observations"} + + Usually some students will complain that you've missed out the three, + but simply indicate that you haven't made a mistake. + This gives them the opportunity to try to construct the pattern for + themselves. + + {panel end} + +5. Ask what the next card is, and why. + + {panel type="teaching" title="Teaching observations"} + + At this point it is common for students to guess that it is 6 + (since it follow the numbers 2 and 4). + However, if you let them think about it a little more, some will usually + come up with 8, and those students should be able to convince the others + that they are correct (there are several ways a student could explain + this e.g. that each card is double the previous one, or that if you take + two of a card, you get the next one) + + {panel end} + +6. Students should be able to work out the fifth card (16 dots) without help: + + {image file-path="img/topics/col_binary_5cards.png" alt="5 kids holding binary cards"} + +7. How many dots would the next card have if we carried on to the left? (32) + The next...? + (There's no need to have students hold these cards, as they won't be used + in the next part of the activity, but you can show them to confirm that + they are correct). + +8. Continue with 64 and 128 dots. + + {panel type="teaching" title="Teaching observations"} + + At 128 dots there would be 8 cards. This is 8 bits, which is commonly + referred to as a byte. + It may be distracting to bring this up at this point, but some students + may already be familiar with the idea that 8 bits is a byte, and make + that observation. + However, in the meantime, we'll work with a 5-bit representation, which + isn't as useful as a whole byte, but a good size for teaching. + (A byte is a convenient grouping of bits, and usually computer storage is + based around bytes rather than individual bits; it's just the same as eggs + being sold as a dozen; they could be sold individually, but groups of a + dozen are usually more convenient for everyone concerned.) + + A common mistake is to hand out the cards from left to right, but it's + convention in number representation that the least significant value is + on the right, and this is an important idea for students to take away + from this activity. + + {panel end} + +## Lesson activities + +1. Remind the students that the rule is that a card either has the dots fully + visible, or none of them are visible. + If we can turn cards on and off by showing the front and back of the card, + how would we show exactly 9 dots? + Begin by asking if they want the 16 card (they should observe that it has + too many dots), then the 8 card (they will likely reason that without it + there aren't enough dots left without it), then 4, 2 and 1. + Without being given any rules other than each card being visible or not, + students will usually come up with the following representation. + + {image file-path="img/topics/binary-cards-total-9.png" alt="Diagram showing that 2 binary cards make the number 9"} + + {panel type="math" title="Mathematical links"} + + Base 10 (our counting system) has 10 digits, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. + When we count in base 10, we count from 0 to 9 and then run out of digits. + So we need to add another column; we put a 1 in that column and start + counting again from 0. + This makes the number 10, we then repeat that process until the tens + column is 9 and the ones column is 9 (making 99); from there we then add + another column. + Hence we have the familiar place value system that can be shown something + like this: + + 100,000s | 10,000s | 1,000s | 100s |10s | 1 + + *Note: Use the appropriate place value example based on what you have + already taught in your class; this is an extended example.* + + Base 2 (binary) follows the same logic, except it moves a lot quicker + to the “next” place value, because there are only two digits, 0 and 1. + The binary place values look like this: + + 32 | 16 | 8 | 4 | 2 | 1 | + + Sometimes students confuse the order of digits in a binary representation. + To support students to understand the correct ordering of binary digits, + ask the question: If I was going to give you $435.00 which number are you + most interested in? Is it the 4 or the 5? + Why is that? + It’s the same for binary code, the lowest value (least significant digit) + is on the far right, whereas the most significant digit is on the far left. + + {panel end} + +2. Now ask "How would you make the number 21?" + (Again, start by asking if they want the 16 card, then the 8 card, and so + on from left to right). + +3. This is an algorithm for converting numbers to a binary representation. Let’s think through the steps to do this together. + + a. Start with all the numbers switched to on (dots showing). + + {image file-path="img/topics/lightbulb_series_1.png" alt="5 lightbulbs switched on"} + + b. Consider representing the number 10 + + c. Does 16 fit into 10? No - so turn it off + + {image file-path="img/topics/lightbulb_series_2.png" alt="4 lightbulbs switched on"} + + d. Does 8 fit into 10? Yes - so keep it on. How many are left over? (2) + + e. Does 4 fit into 2? No - so turn it off + + {image file-path="img/topics/lightbulb_series_3.png" alt="3 lightbulbs switched on"} + + f. Does 2 fit into 2? Yes - so keep it on. How many are left over? (None) + + g. So turn off 1. + + {image file-path="img/topics/lightbulb_series_4.png" alt="2 lightbulbs switched on"} + +## Applying what we have just learnt + +- Group students into pairs. +- Give each pair a set of the smaller binary cards (either 5 or 6 cards, + depending on the range of numbers they are comfortable with). +- Starting with just 5 cards, have them practise the algorithm (deciding + about each card from left to right) for numbers such as 20, 15, and 8. + +1. Explain to students that we're working with just two digits, so they + are called binary digits. + They are so common that we have a short name for them: write "binary + digit" on a piece of paper, then rip off the "bi" at the start, and the + "t" at the end, put it together and ask what the combined word ("bit") + spells. + This is the short name for a binary digit, so the 5 cards that they have + are actually 5 bits. + +2. Now let’s count from the smallest number we can make up to the highest + number: + + a. What is the smallest number? (they may suggest 1, then realise + that it’s 0). + +3. Get the number zero displayed on the cards (i.e. no dots showing). + +4. Now count up 1, 2, 3, 4 …. (each pair should work out these numbers + between them). + +5. Once they start to get into a routine, ask: how often are we seeing the + 1-dot card? (every second time, which is every odd number) + + a. What other patterns are we seeing? + (some may observe that the 2-dot card flips on every second count, + the 4-dot on every 4th and so on; so the 16 dot card doesn't do much!) + +6. Continue until all the cards are switched to “on” and have counted to 31. + What happens next? (We have to add a new card.) + How many dots on it? (32) + What do we have to do to the other 5 cards when we get to 32? + (we have to turn them all off) + +7. Let’s explore this further ... + + a. So when I have two bits I can make a maximum of? (3) + + b. I add another bit and that has how many dots on it? (4) + + c. I turn off the first two bits to make 4 right? + + d. Now let’s turn on all three bits, so now we have how many? (7) + + e. I add another bit and that has how many dots on it? (8) + + f. Repeat until a pattern is recognised that the number on the next card + to the left is one more than the total number of dots on all the cards to + the right (e.g. there are 15 dots on the 8, 4, 2 and 1 cards, so the next + card to the left is 16). + This makes it easy to work out the number if all the bits are switched + on - double the left-hand card, and subtract 1. + + g. How many different numbers can I make with two bits? (4; often students + will say 3 because they haven’t counted 0) + + h. Let’s add the next bit; how many different numbers can we make now? (8, + again 7 will often be given as an answer first) + + i. Repeat until a pattern is recognised that each time we add another bit + we can now represent twice as many numbers. + +{panel type="teaching" title="Teaching observations"} + +A concept that students may struggle with here is that the number of values +is one more than the maximum value (e.g. from 0 to 7, there are 8 different +numbers). +The same observation occurs with the number of digits in conventional decimal +numbers; the largest digit is 9, but there are 10 possible digits (counting 0). +This is sometimes called the fencepost problem (the number of fence posts is +one more than the number of gaps between them), and it comes up a lot in +computing. + +{panel end} + +## Lesson Reflection + +- Would this activity work if we used white and cream cards? Why? Why not? + (In principle you could use these, but it wouldn’t be a good idea. + We are looking for the answer that they are not contrasting colours, + therefore it would be difficult to see if it is actually on or off. + This hints and why computers use easily distinguished physical + representations.) +- What are some contrasting symbols or ways that we can show on and off in + binary? + + - (Ideas could include holding the cards up high, or down low; + simply holding up a hand; sitting down or standing up; or using a + different representation such as lights that are on or off.) + +- Computers are cheaper and easier to build if they represent data with just + two contrasting values, which we represent as the numbers 0 and 1. + What else could we use to represent two opposites in writing? + (Perhaps a cross or tick; happy or sad face; or any other pair of symbols.) +- Extending this idea, the numbers might be represented by a voltage that + is either close to 5 volts, or close to 0 volts. + The circuitry is built so that anything less than about 2.5 volts counts + as 0 and anything over 2.5 volts counts as 1. + Like the contrasting colours of the cards, this is very easy to recognise. + We could have had 10 colours of cards to represent the digits from 0 to + 10, and we could have ten voltage ranges (0 to 0.5, 0.5 to 1.0 and so on), + but it's way more complicated to build fast and accurate circuitry + for this. diff --git a/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/lessons.yaml b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/lessons.yaml new file mode 100644 index 000000000..3d834ef37 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/lessons.yaml @@ -0,0 +1,113 @@ +how-binary-digits-work-junior: + duration: 45 + computational-thinking-links: how-binary-digits-work-junior-ct-links.md + learning-outcomes: + - binary-explain-off-on + - numeracy-identify-larger-number + - numeracy-count-by-pointing + - numeracy-recognise-patterns + generated-resources: + binary-cards: + description: One set for class demonstration. + binary-windows: + description: One set per student. + classroom-resources: + - Number line from 0 to 20 + +reinforcing-sequencing-in-binary-number-systems-junior: + duration: 60 + computational-thinking-links: reinforcing-sequencing-in-binary-number-systems-junior-ct-links.md + learning-outcomes: + - binary-make-play-instructions-binary + - numeracy-count-by-pointing + - binary-identify-opposite-pairs + generated-resources: + binary-cards: + description: One set for class demonstration. + binary-cards-small: + description: One set of cards per student. + classroom-resources: + - Musical instruments + - Glue a stick onto paper plates and have one side on and the other side off + - Hats on and off + +how-binary-digits-work: + duration: 45 + computational-thinking-links: how-binary-digits-work-ct-links.md + programming-challenges: + - binary-numbers-no-calculations + - binary-numbers-using-variable + - binary-numbers-using-variables-as-operator + - binary-numbers-using-variable-operator-repeat + - binary-numbers-one-line + - number-cards-display + - dots-right-left + - dots-largest-to-1 + - count-5-bw-no-loop + - count-5-bw-using-loop + - count-5-bw-one-input-no-loop + - count-5-bw-one-input-using-loop + - count-bw-one-input-using-loop + - cards-given-number + - cards-given-number-one-line + - number-bits-representing-number + - binary-cards-representing-number + - binary-cards-representing-any-number + - decimal-to-binary + - decimal-to-binary-alternative + - binary-notes-representing-number + - binary-notes-representing-any-number + - binary-cards-as-dots + learning-outcomes: + - binary-convert-decimal + - binary-justify-zeros-and-ones + - binary-argue-zeros-and-ones-stored + - binary-explain-different-states + - binary-explain-logic-one-bit + - numeracy-adding-given-number + - numeracy-identify-even-odd + - numeracy-explain-squaring + generated-resources: + binary-cards: + description: One set for class demonstration. + binary-cards-small: + description: One set of cards per student. + classroom-resources: + - Pens and paper, or + - Whiteboard and pens + +reinforcing-sequencing-in-binary-number-systems: + duration: 60 + computational-thinking-links: reinforcing-sequencing-in-binary-number-systems-ct-links.md + learning-outcomes: + - binary-explain-off-on + - binary-predict-bit-value + - binary-explain-doubling + - numeracy-discuss-count-zeros-ones + - numeracy-compare-base10-base2 + - drama-create-video-binary + - speaking-clear-instructions-binary-cards + generated-resources: + binary-cards: + description: One set for class demonstration. + binary-cards-small: + description: One set of cards per student. + classroom-resources: + - None required, but see note in text. + +codes-for-letters-using-binary-representation: + duration: 45 + computational-thinking-links: codes-for-letters-using-binary-representation-ct-links.md + learning-outcomes: + - binary-recognise-alphabet-bits + - binary-create-message-converting + - binary-interpret-message-binary + - binary-explain-codes-alphabets + - language-discuss-storing-characters + generated-resources: + binary-cards: + description: One set for class demonstration. + binary-cards-small: + description: One set of cards per student. + binary-to-alphabet: + description: Blank sheets for students, plus teacher answer sheet. diff --git a/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/reinforcing-sequencing-in-binary-number-systems-ct-links.md b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/reinforcing-sequencing-in-binary-number-systems-ct-links.md new file mode 100644 index 000000000..83241fb73 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/reinforcing-sequencing-in-binary-number-systems-ct-links.md @@ -0,0 +1,47 @@ +{panel type="ct-algorithm" title="Algorithmic thinking"} + +#### Examples of what you could look for: + +Do students demonstrate how the binary number system works by explaining, systematically, what is happening to work out the binary representation of a given number? + +{panel end} + +{panel type="ct-abstraction" title="Abstraction"} + +#### Examples of what you could look for: + +Ask students to look at a video created and list the features of the objects used that are important to demonstrate the binary number system and those that aren’t relevant at all. e.g. using Maths teddy bears - having the back or front showing is important, the colour of the teddy isn’t important, the size of the teddy isn’t important. + +{panel end} + +{panel type="ct-decomposition" title="Decomposition"} + +#### Examples of what you could look for: + +Watch a video and ask students to point out one element in it that is decomposition e.g. this could be when a single bit out of a group is determined to be on or off. + +{panel end} + +{panel type="ct-pattern" title="Generalising and patterns"} + +#### Examples of what you could look for: + +Have students review three videos and ask what do these videos have in common and what is different about them. + +{panel end} + +{panel type="ct-logic" title="Logic"} + +#### Examples of what you could look for: + +Finding errors or misconceptions in the videos may exercise some logical reasoning. + +{panel end} + +{panel type="ct-evaluation" title="Evaluation"} + +#### Examples of what you could look for: + +Have students consider the number of bits used in each demonstration, and the difference this makes in the range of values that can be represented. + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/reinforcing-sequencing-in-binary-number-systems-junior-ct-links.md b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/reinforcing-sequencing-in-binary-number-systems-junior-ct-links.md new file mode 100644 index 000000000..df9def595 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/reinforcing-sequencing-in-binary-number-systems-junior-ct-links.md @@ -0,0 +1,52 @@ +This lesson supports students to apply and recognise the computational thinking links from lesson one. + +Explicit links could include: + +{panel type="ct-algorithm" title="Algorithms"} + +#### Examples of what you could look for: + +Do students demonstrate how the binary number system works by explaining, systematically, what is happening to work out the binary representation of a given number? + +{panel end} + +{panel type="ct-abstraction" title="Abstraction"} + +#### Examples of what you could look for: + +Ask students to look at a demonstration created by other students and list the features of the objects used that are important to demonstrate the binary number system and those that aren’t relevant at all. e.g. using teddy bears - having the back or front showing is important, the colour of the teddy isn’t important, the size of the teddy isn’t important. + +{panel end} + +{panel type="ct-decomposition" title="Decomposition"} + +#### Examples of what you could look for: + +Watch a demonstration and ask students to point out some of the small steps that the students in the demonstration had to do e.g. this could be when a single bit out of a group is determined to be on or off. + +{panel end} + +{panel type="ct-pattern" title="Generalising and patterns"} + +#### Examples of what you could look for: + +Have students review three demonstrations and ask what do these each had in common and what was different about them. +Ask them if they notice any patterns while they are counting. + +{panel end} + +{panel type="ct-evaluation" title="Evaluation"} + +#### Examples of what you could look for: + +Have students consider the number of bits used in each demonstration, and the difference this makes in the range of values that can be represented. + +{panel end} + +{panel type="ct-logic" title="Logic"} + +#### Examples of what you could look for: + +Finding errors or misconceptions in the demonstrations can exercise students logical reasoning. + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/reinforcing-sequencing-in-binary-number-systems-junior.md b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/reinforcing-sequencing-in-binary-number-systems-junior.md new file mode 100644 index 000000000..8fcbe4002 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/reinforcing-sequencing-in-binary-number-systems-junior.md @@ -0,0 +1,88 @@ +# Reinforcing sequencing in binary number systems + +## Key question + +- How can you create a play to teach someone how binary works, without using the binary cards? + +## Lesson starter + +{panel type="general" title="Notes on resources"} + +The classroom resources we suggested are highly motivating, however please use any resources you have available in your classroom that have two different states, sounds or sizes. + +{panel end} + +Computers use on and off to know how to display information or data. +We can use anything we like that is opposite to each other. + +Today we will look at what opposites are and how we can count using these. +If I said “happy face” what would be the opposite? + +What about a cat? + +Introduce hats - hat on or hat off - or paper plates - one side on and one side off - and demonstrate how you would make the number 1, then 2, then 3. + +Your play must include a demonstration of one of the following: + +- How to count from 0 to 10 or more using binary dots. +- How you would work out the number 5. + +## Lesson activities + +Brainstorm together items you could use to make the numbers. +It should be different to how it has been demonstrated to the class, so they are not allowed to use cards with dots on them. + +In groups of 4 have students decide how they will show ON and OFF. +They need to choose which bit they are going to be. +(It may help to give them each their own bit card so they remember their number). +As well as physical representations like hats, it could be using words (e.g. saying "yes and no"), or musical sounds (high and low, long and short, loud and soft), or light. + +8, 4, 2, 1 + +Have a chance to practise counting in their group from 1 to 8. + +Once they have done that they change roles to they experience what it's like being each of the different bits: + +- The person with 8 takes on the job of 1 +- 4 becomes 8 +- 2 becomes 4 +- 1 becomes 2 + +When they are ready ask each group to make the number (choose a random number between 0 and 8). + +Your plays will typically need 4 students in a group to represent the four bits. +If the number of students isn't a multiple of 4, the extra students could take on roles such as taking photos or videos. +The roles can swap over so they have a turn at being a “bit” as well as capturing the learning. +Alternatively, a smaller group could explore other options, such as one student holding two bits, or using objects as bits on 4 chairs "operated" by the students, so one student can change all of them. + +{panel type="teacher-observation" title="Teaching observations"} + +1. The number pattern is from highest to lowest, left to right (it's just a convention, but we use the same one in the decimal number system, with the most significant digits on the left): + + *8, 4, 2, 1* + + Therefore watch that they haven’t switched the order of the place values, for example they check they aren’t representing the decimal number 1 as: + + *1, 0, 0, 0 (which actually represents the decimal number 8)* + +2. Below is an example of where students believed that 3 in decimal is represented as 0100, as they focused on the pattern of numbers rather than using the place values. + *0001 + 0010 + 0100 (where it should be 00011 for 3)* + +{panel end} + +## Applying what we have just learnt + +Reflect after each play is performed to reinforce learning by asking: + +1. How did they choose to show on and off? + +2. What made the plays interesting or appealing that another person might be able to learn from them? + +## Lesson reflection + +- What did you clarify about the binary number system now that you’ve created your play? + +- What questions do you have after performing your play in relation to the binary number system? ( + Typically responses are that they want to understand further how binary numbers are used to show letters, images, videos and all things on a computer - these topics are covered in further lesson plans) diff --git a/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/reinforcing-sequencing-in-binary-number-systems.md b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/reinforcing-sequencing-in-binary-number-systems.md new file mode 100644 index 000000000..36719117d --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/unit-plan/lessons/reinforcing-sequencing-in-binary-number-systems.md @@ -0,0 +1,128 @@ +# Reinforcing sequencing in binary number systems + +## Key question + +- How can you create a 60 second or less video to teach someone how binary works without using the binary cards? + +## Lesson starter + +{panel type="general" title="Notes on resources"} + +The following items are highly motivating, however you can use any resources you have available in your classroom that have two different states or sizes. + +- Playdough/plasticine +- Musical instruments +- Mathematics equipment such as mini teddy bears +- Glue a stick onto paper plates and have one side on and the other side off + +There is also an online interactive version of the binary cards [here](http://www.csfieldguide.org.nz/en/interactives/binary-cards/index.html?digits=8), from the [Computer Science Field Guide](http://www.csfieldguide.org.nz/), but it is preferable to work with physical cards. + +{panel end} + +Teacher says to class: “We are going to create a video that shows binary +numbers in use.” + +## Lesson activities + +In this lesson students will make a video that gives instructions on how to +represent any given number in binary. +The video must include a demonstration of one of the following: + +- How to count from 0 to 8 or more using binary numbers. +- How you would work out the number 22. + +Brainstorm together items you could use to make this video with. +It should be different to how it has been demonstrated to the class, so they +are not allowed to just video cards with dots on them (but they may want to +use them to help with their planning). + +Within the hour students will have taken the raw footage needed to make the +movie and imported it into a simple movie format using available software +like iMovie or StopMotion. + +Early finishers can edit their work, add music and then be camera crew +for others. + +Tips to before sending off for the 60 minute challenge: + +1. The order of place values in a binary number is the same as the +conventional (decimal) place value system, so the highest is on the left, smallest value on the right. + +2. Make sure that if you are using colours or other representations there is a strong contrast. + +{panel type="teaching" title="Teaching observations"} + +1. The number pattern is from highest to lowest, left to right (it's just a + convention, but we use the same one in decimal, with the most significant + digits on the left): + + *16, 8, 4, 2, 1* + + Therefore watch that they aren’t representing 1 as: + + *1, 0, 0, 0, 0* (which actually represents the decimal number 16) + +2. Below is an example of where students believed that 3 in decimal is + represented as 00100, as they focused on the pattern of numbers rather + than using the place values. + + *00001* + + *00010* + + *00100* (where it should be 00011 for 3) + +3. The videos below were made within 60 minutes, to provide you with + realistic videos that your students can expand on. + Like all completed work, there is always something you can improve on. + It's very hard to get every detail exactly right when you are explaining + things, so the important thing is that students are demonstrating that + they have the big concepts, rather than getting every detail right; + minor errors such as an incorrect number can be discussion points for + other students. + +{panel end} + +{panel type="general" title="Examplars"} + +Samples of work after an hour challenge: + +**Binary numbers: Using trailer on iMovie** + +{video url="https://youtu.be/-B0CnXv6pzo"} + +**Percussion binary** + +{video url="https://youtu.be/0sQtZhAYUbU"} + +**Binary teddies** + +{video url="https://youtu.be/KNC1CgmVOus"} + +**Binary guy** + +{video url="https://youtu.be/9CzELd9DFwc"} + +{panel end} + +## Applying what we have just learn + +Review each other’s videos + +1. Can you recognise any misconceptions about the binary number system that + have appeared in a video? + +2. What made the videos interesting or appealing that another person might + be able to learn from them? + +## Lesson reflection + +- How can you use these videos to support others to learn about the + binary number system? +- What were the key points that are now clear about the binary number + system now that you’ve created your video? +- What questions do you have after making this video in relation to the + binary number system? + (Typically responses are that they want to understand further how binary + numbers are used to show letters, images, videos and all things + on a computer - these topics are covered in further lesson plans) diff --git a/csunplugged/topics/content/en/binary-numbers/unit-plan/unit-plan-ct-links.md b/csunplugged/topics/content/en/binary-numbers/unit-plan/unit-plan-ct-links.md new file mode 100644 index 000000000..60eade5e8 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/unit-plan/unit-plan-ct-links.md @@ -0,0 +1,60 @@ +{panel type="ct-algorithm" title="Algorithmic thinking"} + +There are several important algorithms for students to learn and follow in +this unit. +These algorithms are solutions to the problems “How do we convert between +decimal and binary numbers?”, “How do we represent letters using binary +numbers?”, and “How do we convert a message written in a binary code back to +letters?”. +Students will be tasked with following these algorithms accurately, +articulating them, and explaining them. + +{panel end} + +{panel type="ct-abstraction" title="Abstraction"} + +Binary number representation is an abstraction that hides the complexity of the +electronics and hardware inside a computer that store data. +Abstraction helps us simplify things because we can ignore the details we don’t +currently need to know. + +{panel end} + +{panel type="ct-decomposition" title="Decomposition"} + +An example of decomposition is breaking the conversion of the number to binary +into one bit at a time. +The questions "Should this be 1 or 0" for each of the dot cards is decomposing +the problem to a series of questions. + +{panel end} + +{panel type="ct-pattern" title="Generalising and patterns"} + +Recognising patterns in the way the binary number system works helps give us a +deeper understanding of the concepts involved, and assists us in generalising +these concepts and patterns so that we can apply them to other problems. + +{panel end} + +{panel type="ct-logic" title="Logic"} + +Logical thinking means using rules you already know and using logic to deduce +more rules and information from these. +Once we know what number each of the binary cards represents then we can use +this knowledge to figure out how to represent other numbers with the cards. +If you memorise how to represent the numbers we can make with 5 cards, does +that mean you understand how to represent any number with any number of bits? +It doesn’t, but you can understand how to do that if you understand the logic +behind how these numbers with the 5 cards are made. + +{panel end} + +{panel type="ct-evaluation" title="Evaluation"} + +An example of evaluation is working out how many different values can be +represented by a given number of bits (e.g. 5 bits can represent 32 different +values), and vice versa (to represent 1000 different values, you need at least +10 bits). + +{panel end} diff --git a/csunplugged/topics/content/en/binary-numbers/unit-plan/unit-plan.md b/csunplugged/topics/content/en/binary-numbers/unit-plan/unit-plan.md new file mode 100644 index 000000000..c5f7a9e78 --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/unit-plan/unit-plan.md @@ -0,0 +1,80 @@ +# Binary Numbers Unit Plan + +## See Teaching the Binary Number System in Action! + +Binary (Computer Science Unplugged Part 1) - these are place-holder videos, new videos are planned to be developed to represent each lesson plan. + +{video url="https://www.youtube.com/embed/Wy6-FXtLMV8"} + +## What’s It All About? + +Computers today use digits to represent information - that's why they're called digital systems. +The simplest and most common way to represent digits is the binary number system, with just two digits (usually written as 0 and 1). +It is called binary because there are only two different digits used, or two states. + +A bit is usually stored in a memory cell inside a computer, which is an electronic circuit that can be set to a high voltage level (1) or a low voltage level (0); on disks they are represented by magnetism or optical reflection. + +There are billions of these bits on a typical computer, and they are used to store text, numbers, images, video, and anything else that we need to store or transmit. On computer networks the bits are communicated by light, voltages or sound. +Anything that can have two different values can represent a bit! + +## Digital Technologies | Data Representation + +The binary number system plays a central role in how information of all kinds is stored on computers. +Understanding binary representation can lift a lot of the mystery from computers, because at a fundamental level they’re really just machines for flipping binary digits on and off. +Computers are simple machines, and they need very exact instructions to make them do complex tasks. + +Teaching binary numbers as an introduction to computational thinking introduces students to {glossary-link term="algorithm"}algorithms{glossary-link end} and decomposition, as they learn to break down the problems of calculating binary numbers and converting between binary and decimal numbers, into step by step processes that they can follow to solve these problems; it also introduces abstraction, as students learn that two different things can be used to represent any and all information. +It shows them that a computer isn’t actually that complex, and we use simple concepts in a clever way to make computers do extraordinary things! + +## Vocabulary Explained + +### Binary Digits + +Each zero or one is called a bit. Bit is an abbreviation for Binary Digit. + +### Bytes + +8 bits grouped together are called a byte, and computers store data in bytes. +A byte is a convenient number of bits as it can store things like simple characters, small numbers, and a useful range of colours, although usually information is stored using groups of bytes. + +{image file-path="img/topics/col_binary_bite_vs_byte.png"} + +{panel type="math" title="Mathematical links"} + +The binary number system is base 2, so there are only two digits before you need to move to the next place value: 0, 1. + +Humans normally use the decimal number system, which is base 10, so there are ten digits before you move to the next place value: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. + +{image file-path="img/topics/col_binary_CSU_boy_hands.png"} + +{image file-path="img/topics/col_binary_csu_girl_hands.png"} + +{panel end} + +## Real World Implications + +{image file-path="img/topics/col_binary_0.png"} + +{image file-path="img/topics/col_binary_1.png"} + + +- The number of bits used to represent characters in text affects the range of characters available; a short representation is more compact, but can't represent characters from all languages. + +- The number of bits used to represent colours in images affects how accurate the colours are. + +- The number of bits used to store sound samples affects the quality of the sound or images. + +- When designing a new application (perhaps storing genetic data or representing smells), the right number of bits to store a symbol needs to be selected - enough to be accurate, but not so many that it's wasteful. + +- In encryption and security applications, the more bits in a security key, the more secure it is. + +- When you purchase a device, measurements in bits relate to how much memory it has and how much data can be stored on different external storage devices. + +- Working with only two digits makes it much easier to build reliable circuitry for digital devices. + +## Reflection Questions + +- What was most surprising about the learning that happened from the teaching of this unit? +- Who were the students who were very systematic in their activities? +- Who were the students who were very detailed in their activities? +- What would I change in my delivery of this unit? diff --git a/csunplugged/topics/content/en/binary-numbers/unit-plan/unit-plan.yaml b/csunplugged/topics/content/en/binary-numbers/unit-plan/unit-plan.yaml new file mode 100644 index 000000000..126662d9f --- /dev/null +++ b/csunplugged/topics/content/en/binary-numbers/unit-plan/unit-plan.yaml @@ -0,0 +1,16 @@ +lessons: lessons/lessons.yaml +computational-thinking-links: unit-plan-ct-links.md + +age-groups: + 5-7: + how-binary-digits-work-junior: + number: 1 + reinforcing-sequencing-in-binary-number-systems-junior: + number: 2 + 8-10: + how-binary-digits-work: + number: 1 + reinforcing-sequencing-in-binary-number-systems: + number: 2 + codes-for-letters-using-binary-representation: + number: 3 diff --git a/csunplugged/topics/content/en/curriculum-areas.yaml b/csunplugged/topics/content/en/curriculum-areas.yaml new file mode 100644 index 000000000..5c7108b4b --- /dev/null +++ b/csunplugged/topics/content/en/curriculum-areas.yaml @@ -0,0 +1,121 @@ +computational-thinking: + name: Computational Thinking + colour: light-purple + number: 1 + children: + algorithmic-thinking: + name: Algorithmic Thinking + decomposition: + name: Decomposition + abstraction: + name: Abstraction + generalising-and-patterns: + name: Generalising and Patterns + logic: + name: Logic + evaluation: + name: Evaluation + +computer-science: + name: Computer Science + colour: purple + number: 2 + children: + data-representation: + name: Data Representation + algorithms: + name: Algorithms + programming: + name: Programming + +mathematics: + name: Mathematics + colour: blue + number: 3 + children: + geometry: + name: Geometry + measurement: + name: Measurement + numeracy: + name: Numeracy + algebra: + name: Algebra + statistics: + name: Statistics + +literacy: + name: Literacy + colour: red + number: 4 + children: + reading: + name: Reading + writing: + name: Writing + listening: + name: Listening + speaking: + name: Speaking + library: + name: Library + +art: + name: Art + colour: orange + number: 5 + +language: + name: Language Learning + colour: pink + number: 6 + +performing-arts: + name: Performing Arts + colour: orange + number: 7 + children: + music: + name: Music + dance: + name: Dance + drama: + name: Drama + +physical-education: + name: Physical Education + colour: orange + number: 8 + children: + fitness: + name: Fitness + sport: + name: Sport + health: + name: Health + +science: + name: Science + colour: green + number: 9 + children: + biology: + name: Biology + physics: + name: Physics + astronomy: + name: Astronomy + chemistry: + name: Chemistry + geology: + name: Geology + +social-studies: + name: Social Studies + colour: teal + number: 10 + children: + history: + name: History + geograph: + name: Geography diff --git a/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/biographies-and-error-control-history.md b/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/biographies-and-error-control-history.md new file mode 100644 index 000000000..465ef9f76 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/biographies-and-error-control-history.md @@ -0,0 +1,28 @@ +# Biographies and error control history + +Imagine you have been asked to introduce one of the following people, write an introduction that explains why they are famous and some interesting information about them. + +Or + +Write a biography of one of the following people: what situation were they living in, what did they achieve, and what impact has that had on society? + +A biography: + +- Gives an account of a person’s life +- Start by introducing the person and what they achieved +- Write about the significant events that happened in their lives +- Write about the impact their work has had on other people + +{image file-path="img/topics/shannon-juggling.png" caption="Claude Shannon" alt="person juggling on unicycle"} + +Richard Hamming was one of the first people to come up with ideas for detecting and correcting errors on computers, and Claude Shannon developed these and related ideas so much that he became known as the "father of information theory". +Both lived in the 20th century, but their ideas are used on every 21st century digital device to make it reliable and efficient. +Below are the names of these and other people whose work in error detection and correction are key to modern digital devices. +We've given some links to Wikipedia below to get you started, but make sure you look more widely than that. + +Some people to research about are: + +- Richard Hamming (very early ideas in error control) [https://en.wikipedia.org/wiki/Richard_Hamming](https://en.wikipedia.org/wiki/Richard_Hamming) +- Claude Shannon (American mathematician, juggler, and unicyclist - you should check out some of the interesting and entertaining gadgets that he invented) [https://en.wikipedia.org/wiki/Claude_Shannon](https://en.wikipedia.org/wiki/Claude_Shannon) +- Hans Peter Luhn (developed a checksum algorithm that is widely used, especially in bank and credit card numbers) [https://en.wikipedia.org/wiki/Hans_Peter_Luhn](https://en.wikipedia.org/wiki/Hans_Peter_Luhn) +- Irving S. Reed and Gustave Solomon (developed one of the most widely used error control codes, used on most digital devices) [https://en.wikipedia.org/wiki/Irving_S._Reed https://en.wikipedia.org/wiki/Gustave_Solomon](https://en.wikipedia.org/wiki/Irving_S._Reed https://en.wikipedia.org/wiki/Gustave_Solomon) diff --git a/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/curriculum-integrations.yaml b/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/curriculum-integrations.yaml new file mode 100644 index 000000000..add56dbae --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/curriculum-integrations.yaml @@ -0,0 +1,36 @@ +quick-card-flip-magic: + number: 1 + curriculum-areas: + - numeracy + prerequisite-lessons: + unit-plan: + - parity-magic + +what-if: + number: 2 + curriculum-areas: + - literacy + prerequisite-lessons: + unit-plan: + - parity-magic + +biographies-and-error-control-history: + number: 3 + curriculum-areas: + - literacy + +instructional-writing: + number: 4 + curriculum-areas: + - literacy + prerequisite-lessons: + unit-plan: + - parity-magic + +word-study-activity: + number: 5 + curriculum-areas: + - literacy + prerequisite-lessons: + unit-plan: + - parity-magic diff --git a/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/instructional-writing.md b/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/instructional-writing.md new file mode 100644 index 000000000..f4621e1a5 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/instructional-writing.md @@ -0,0 +1,4 @@ +# Instructional writing + +- Write instructions on how to play the parity card game so that others can play it. +- Write instructions on how to add up a check digit for a 12 digit barcode so that someone else can do it. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/quick-card-flip-magic.md b/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/quick-card-flip-magic.md new file mode 100644 index 000000000..a2aa7ae7b --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/quick-card-flip-magic.md @@ -0,0 +1,45 @@ +# Quick card flip magic + +{image file-path="img/topics/kids-parity-trick.png"} + +*Every item of data that we store and transmit has extra bits added to it to prevent errors. +Can you find the error and correct it?* + +### Equipment: + +At least 36, or even 100 or more cards that are black on one side and white on the other, about 20mm by 20mm (or any two colours that are easily distinguished) +A clear space on the floor or a table that the students can stand around + +### Preparation: + +Select a ‘magic master’. +This is the person who is in control of the game. +This person will change at the end of each round. + +### Instructions: + +1. The magic master sets up the cards on the floor, including parity cards in the last row and column, using someone from the group as their assistant. + The grid can be any size; it should be at least 6 by 6, although it can be increased up to 10 by 10 or more to make the challenge harder. + The grid doesn't have to be square (e.g. 9 by 8 is fine), but the effect is greatest when it is close to square. + +2. The magic master asks everyone to close their eyes and turn away, except for the magic master and the assistant. + +3. The magic master asks the assistant to choose a card, place a counter or a mark under where the card goes and flip it over. + +4. Once this has been done, the magic master calls out (quietly)…” let the magic begin” and presses the timer. + When the other students hear this they turn around and try to find the ‘error’. + +5. As soon as they spot the error, they put their finger on their nose. + +6. The magic master stops the timer and asks the first person who put their finger on their nose to show where the flipped card was. + +7. The student points to the flipped card, checks if they are correct by flipping the card over. + +8. If they are correct, that person explains how they worked out which was the flipped card. + +9. The magic master records the person's name who won that round and the time. + +10. Did they beat the previous time? + +11. If they did beat the previous time, that person becomes the assistant. + The person who won that round stays the assistant until their time is beaten. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/what-if.md b/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/what-if.md new file mode 100644 index 000000000..9e4878162 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/what-if.md @@ -0,0 +1,10 @@ +# What if? + +{image file-path="img/topics/gobbly-gook-computer.png" alt="computer with strange symbols instead of plain text"} + +What if computers had no way of finding out if an error had occurred when reading a file or receiving transmitted data? +Where do you think we would be with technology? Write a report that explains this. + +An example of what this could look like is your friend sends you a file and you open it, but some of the files data has been changed so that when you open it you can see some words, but there is some “gobbledygook” writing as well. + +Another example is you have clicked to open an app on your device, but it doesn’t open and just freezes. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/word-study-activity.md b/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/word-study-activity.md new file mode 100644 index 000000000..346cf2d2b --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/curriculum-integrations/word-study-activity.md @@ -0,0 +1,8 @@ +# Word study activity + +{image file-path="img/topics/par-pair.png" alt="golf bag with golf sticks"} + +- What are the dictionary meanings of the word "parity". +- What Latin word does it come from? +- What other common words derive from that Latin word? + (One of the words is used for matching socks; another is used when playing golf). diff --git a/csunplugged/topics/content/en/error-detection-and-correction/error-detection-and-correction.md b/csunplugged/topics/content/en/error-detection-and-correction/error-detection-and-correction.md new file mode 100644 index 000000000..e65e41efe --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/error-detection-and-correction.md @@ -0,0 +1,11 @@ +# Error detection and correction + +The world is a complicated and imperfect place, and errors can occur when +digital information is stored or transmitted. +Data stored on digital devices can be changed if there is a tiny fault in the +hardware (and these occur regularly); and data being transmitted over networks +can be messed up if there's a problem with the connection (which is bound to +happen at some stage). +This unit and lessons explores how digital devices detect and correct errors in +data, and why sometimes computers can detect errors, but don’t have enough +information to correct them. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/error-detection-and-correction.yaml b/csunplugged/topics/content/en/error-detection-and-correction/error-detection-and-correction.yaml new file mode 100644 index 000000000..d9e001abe --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/error-detection-and-correction.yaml @@ -0,0 +1,7 @@ +unit-plans: + - unit-plan/unit-plan.yaml + +icon: img/topics/parity-trick-example-icon.png +programming-challenges: programming-challenges/programming-challenges.yaml +curriculum-integrations: curriculum-integrations/curriculum-integrations.yaml +other-resources: other-resources.md diff --git a/csunplugged/topics/content/en/error-detection-and-correction/other-resources.md b/csunplugged/topics/content/en/error-detection-and-correction/other-resources.md new file mode 100644 index 000000000..31d0f60c6 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/other-resources.md @@ -0,0 +1,3 @@ +# Other Resources + +The [Computer Science Field Guide](http://csfieldguide.org.nz/) has a chapter that covers [Error Control](http://csfieldguide.org.nz/en/chapters/coding-error-control.html) in more detail, and has an online interactive of the [Parity Trick](http://csfieldguide.org.nz/en/interactives/parity/index.html). \ No newline at end of file diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-credit-card-last-digit/calculate-credit-card-last-digit.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-credit-card-last-digit/calculate-credit-card-last-digit.md new file mode 100644 index 000000000..4bba8d0ac --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-credit-card-last-digit/calculate-credit-card-last-digit.md @@ -0,0 +1,20 @@ +# Calculate the last digit of a credit card number + +## Requirement: + +Write a program that asks the user to enter the first 15 digits of a credit +card number as the input and says what the last digit should be as the output. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +*Important: None of these is an actual credit card number, but the correct +ones have the correct checksum.* + +| Input | Output | +| --------------- | --------------------------------------- | +| 510510510510510 | The last digit of the credit card is: 0 | +| 411111111111111 | The last digit of the credit card is: 1 | +| 401288888888188 | The last digit of the credit card is: 1 | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-credit-card-last-digit/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-credit-card-last-digit/scratch-expected.md new file mode 100644 index 000000000..23c584ec6 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-credit-card-last-digit/scratch-expected.md @@ -0,0 +1,54 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148477813/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +say (join [The last digit of the credit card should be: ] (((0) - ((total 2) + (total 1))) mod (10))) + +ask [Enter the first 15 digits of the credit card:] and wait +``` + +```scratch:split:random +if <((letter (index) of (first 15 digits)) * (2)) < [10]> then +else +end + +repeat (7) +end + +repeat (8) +end +``` + +```scratch:split:random +set [index v] to [1] + +set [first 15 digits v] to [0] + +set [total 2 v] to [0] + +set [total 1 v] to [0] + +set [first 15 digits v] to (answer) + +change [total 1 v] by ((letter (index) of (first 15 digits)) * (2)) + +change [index v] by (2) + +change [total 1 v] by (((letter (index) of (first 15 digits)) * (2)) - (9)) + +change [index v] by (2) + +set [index v] to [2] + +change [total 2 v] by (letter (index) of (first 15 digits)) + +change [index v] by (2) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-credit-card-last-digit/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-credit-card-last-digit/scratch-hints.md new file mode 100644 index 000000000..74c531547 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-credit-card-last-digit/scratch-hints.md @@ -0,0 +1,12 @@ +- For the first 15 digits of the credit card, multiply every odd digit by 2 + (i.e. 1st, 3rd, 5th, and so on digits), but if this gives a 2-digit + number you should subtract 9 from it. + For example, if the input digit 8 is multiplied by 2, that gives 16, and + subtracting 9 from that gives 7 to add to the running total (some + descriptions of the method say that you should add the two digits + together, which gives the same result e.g. for 8x2 = 16 adding the + digits in the result is 1+6, which is also 7). + +- You then add the sum of these values to the sum of the even digits. + The last digit (check digit) is calculated by subtracting the final + total from 0 mod 10. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-credit-card-last-digit/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-credit-card-last-digit/scratch-solution.md new file mode 100644 index 000000000..1505449a1 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-credit-card-last-digit/scratch-solution.md @@ -0,0 +1,24 @@ +```scratch +when green flag clicked +set [index v] to [1] +set [first 15 digits v] to [0] +set [total 2 v] to [0] +set [total 1 v] to [0] +ask [Enter the first 15 digits of the credit card:] and wait +set [first 15 digits v] to (answer) +repeat (8) + if <((letter (index) of (first 15 digits)) * (2)) < [10]> then + change [total 1 v] by ((letter (index) of (first 15 digits)) * (2)) + change [index v] by (2) + else + change [total 1 v] by (((letter (index) of (first 15 digits)) * (2)) - (9)) + change [index v] by (2) + end +end +set [index v] to [2] +repeat (7) + change [total 2 v] by (letter (index) of (first 15 digits)) + change [index v] by (2) +end +say (join [The last digit of the credit card should be: ] (((0) - ((total 2) + (total 1))) mod (10))) +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-isbn-10-last-digit/calculate-isbn-10-last-digit.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-isbn-10-last-digit/calculate-isbn-10-last-digit.md new file mode 100644 index 000000000..a908a23b9 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-isbn-10-last-digit/calculate-isbn-10-last-digit.md @@ -0,0 +1,18 @@ +# Calculate the last digit of an ISBN-10 number + +## Requirement: + +Write a program that asks the user to enter the first 9 digits of an +ISBN-10 number as the input and says what the last digit should be. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +| --------- | -------------------- | +| 020153082 | The last digit is: 1 | +| 014003219 | The last digit is: 3 | +| 052135741 | The last digit is: 1 | +| 043942089 | The last digit is: X | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-isbn-10-last-digit/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-isbn-10-last-digit/scratch-expected.md new file mode 100644 index 000000000..78020ae71 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-isbn-10-last-digit/scratch-expected.md @@ -0,0 +1,51 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148477907/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Enter the first 9 digits of an ISBN-10 number:] and wait +``` + +```scratch:split:random +say [The last digit is: X] + +say (join [The last digit is: ] (last digit)) +``` + +```scratch:split:random +set [index v] to [1] + +set [multiplier v] to [10] + +set [first 9 digits v] to [0] + +set [total v] to [0] + +set [last digit v] to [0] + +set [first 9 digits v] to (answer) + +change [total v] by ((multiplier) * (letter (index) of (first 9 digits))) + +change [index v] by (1) + +change [multiplier v] by (-1) + +set [last digit v] to (((0) - (total)) mod (11)) +``` + +```scratch:split:random +if <(last digit) = [10]> then +else +end + +repeat (9) +end +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-isbn-10-last-digit/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-isbn-10-last-digit/scratch-hints.md new file mode 100644 index 000000000..03d419c48 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-isbn-10-last-digit/scratch-hints.md @@ -0,0 +1,23 @@ +- Calculate the sum of all the 9 digits each multiplied by its (integer) + weight, descending from 10 to 2. + The last digit (check digit) is calculated by subtracting the final + total from 0 mod 11. + +- If the last digit is the number 10, replace it with the letter ‘X’. + + For example the last digit of the ISBN-10 “020153082” is calculated by: + + \[ total=(100)+(92)+(80)+(71)+(65)+(53)+(40)+(38)+(22) \] + + \[ total=0+18+0+7+30+15+0+24+4=98 \] + + So the last digit is 1 (i.e. (0-98) mod 11 which is equal to 1). + + Another example: The last digit of the ISBN-10 “043942089” is + calculated by: + + \[ total=(100)+(94)+(83)+(79)+(64)+(52)+(40)+(38)+(29) \] + + \[ total=0+36+24+63+24+10+0+24+18=199 \] + + So the last digit is ‘X’ (i.e. (0-199) mod 11 which is equal to 10). diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-isbn-10-last-digit/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-isbn-10-last-digit/scratch-solution.md new file mode 100644 index 000000000..07268e053 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/calculate-isbn-10-last-digit/scratch-solution.md @@ -0,0 +1,21 @@ +```scratch +when green flag clicked +set [index v] to [1] +set [multiplier v] to [10] +set [first 9 digits v] to [0] +set [total v] to [0] +set [last digit v] to [0] +ask [Enter the first 9 digits of an ISBN-10 number:] and wait +set [first 9 digits v] to (answer) +repeat (9) + change [total v] by ((multiplier) * (letter (index) of (first 9 digits))) + change [index v] by (1) + change [multiplier v] by (-1) +end +set [last digit v] to (((0) - (total)) mod (11)) +if <(last digit) = [10]> then + say [The last digit is: X] +else + say (join [The last digit is: ] (last digit)) +end +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-mod/check-odd-even-using-mod.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-mod/check-odd-even-using-mod.md new file mode 100644 index 000000000..3f860d43e --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-mod/check-odd-even-using-mod.md @@ -0,0 +1,18 @@ +# Check for odd/even numbers using modulo operation + +## Requirement: + +Create a program which asks the user to enter a number and checks if the +number is odd or even. +Use the "modulo" operation for this challenge. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +| ----- | --------------------------- | +| 265 | You entered an odd number! | +| 0 | You entered an even number! | +| -22 | You entered an even number! | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-mod/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-mod/scratch-expected.md new file mode 100644 index 000000000..b70b0f766 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-mod/scratch-expected.md @@ -0,0 +1,26 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148426036/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Enter a number:] and wait + +set [number v] to (answer) + +if <((number) mod (2)) = [0]> then +else +end +``` + +```scratch:split:random +say [You entered an even number!] + +say [You entered an odd number!] +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-mod/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-mod/scratch-hints.md new file mode 100644 index 000000000..7fa8bc6d0 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-mod/scratch-hints.md @@ -0,0 +1,5 @@ +- To find out if a number is even or odd, use the `scratch:() mod ()` block + (under "Operators") to find the remainder after dividing that number by + two. + If the remainder is zero the number is even. + For example: `scratch:(37) mod (10) //7` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-mod/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-mod/scratch-solution.md new file mode 100644 index 000000000..27c8d275e --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-mod/scratch-solution.md @@ -0,0 +1,10 @@ +```scratch +when green flag clicked +ask [Enter a number:] and wait +set [number v] to (answer) +if <((number) mod (2)) = [0]> then + say [You entered an even number!] +else + say [You entered an odd number!] +end +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-subtraction/check-odd-even-using-subtraction.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-subtraction/check-odd-even-using-subtraction.md new file mode 100644 index 000000000..26c4bf47f --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-subtraction/check-odd-even-using-subtraction.md @@ -0,0 +1,20 @@ +# Check for odd/even numbers using repeated subtraction + +## Requirement: + +Create a program which asks the user to enter a number and checks if +the number is odd or even. +You should do this by keep subtracting 2 from your number until it is 0 or 1 +(i.e. If it is 0 then the number is even and if it is 1 the number is odd). +Make sure your program works for negative numbers as well. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +| ----- | --------------------------- | +| 265 | You entered an odd number! | +| 0 | You entered an even number! | +| -22 | You entered an even number! | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-subtraction/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-subtraction/scratch-expected.md new file mode 100644 index 000000000..3ed450f33 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-subtraction/scratch-expected.md @@ -0,0 +1,44 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148426073/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Enter a number:] and wait + +change [number v] by (-2) +``` + +```scratch:split:random +if <(number) < [0]> then +end + +repeat until <<(number) = [0]> or <(number) = [1]>> +end + +if <(number) = [0]> then +end + +if <(number) = [1]> then +end +``` + +```scratch:split:random +say [You entered an odd number!] + +say [You entered an even number!] +``` + +```scratch:split:random +set [number v] to [0] + +set [number v] to (answer) + +set [number v] to ((-1) * (number)) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-subtraction/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-subtraction/scratch-hints.md new file mode 100644 index 000000000..8dbab9b90 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-subtraction/scratch-hints.md @@ -0,0 +1,8 @@ +- To check if a number is greater, less than or equal to another number + use the `scratch:<[] < []>`, `scratch:<[] > []>`, or `scratch:<[] = []>` + blocks under “Operators”. +- You can make a negative number positive by multiplying it by -1. +- The `scratch:< <> or <> >` operation under “Operators” joins two + boolean blocks so any one of them can be true to return *true*. + If at least one of them is true, the block returns *true*. + If neither of them are true, it returns *false*. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-subtraction/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-subtraction/scratch-solution.md new file mode 100644 index 000000000..45f705761 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-odd-even-using-subtraction/scratch-solution.md @@ -0,0 +1,18 @@ +```scratch +when green flag clicked +set [number v] to [0] +ask [Enter a number:] and wait +set [number v] to (answer) +if <(number) < [0]> then + set [number v] to ((-1) * (number)) +end +repeat until <<(number) = [0]> or <(number) = [1]>> + change [number v] by (-2) +end +if <(number) = [0]> then + say [You entered an even number!] +end +if <(number) = [1]> then + say [You entered an odd number!] +end +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-credit-card/check-valid-credit-card.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-credit-card/check-valid-credit-card.md new file mode 100644 index 000000000..bf865cb66 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-credit-card/check-valid-credit-card.md @@ -0,0 +1,23 @@ +# Check for valid credit card number + +## Requirement: + +Write a program that asks the user to enter a credit card number (16 digits) +as the input and says if the credit card number is valid as the output. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +*Important: None of these is an actual credit card number, but the correct +ones have the correct checksum.* + +| Input | Output | +| ---------------- | --------------------------- | +| 5105105105105100 | Valid credit card number! | +| 4111111111111111 | Valid credit card number! | +| 4012888888881881 | Valid credit card number! | +| 5105105505105100 | Invalid credit card number! | +| 4111112111111111 | Invalid credit card number! | +| 4112888888881881 | Invalid credit card number! | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-credit-card/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-credit-card/scratch-expected.md new file mode 100644 index 000000000..dfda8b049 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-credit-card/scratch-expected.md @@ -0,0 +1,58 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148477751/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Enter a credit card number:] and wait +``` + + +```scratch:split:random +repeat (8) +end + +if <((letter (index) of (credit card number)) * (2)) < [10]> then +else +end + +if <(((even total) + (odd total)) mod (10)) = [0]> then +else +end +``` + +```scratch:split:random +say [Invalid credit card number!] + +say [Valid credit card number!] +``` + +```scratch:split:random +set [index v] to [1] + +set [credit card number v] to [0] + +set [even total v] to [0] + +set [odd total v] to [0] + +change [credit card number v] by (answer) + +change [odd total v] by ((letter (index) of (credit card number)) * (2)) + +change [index v] by (1) + +change [odd total v] by (((letter (index) of (credit card number)) * (2)) - (9)) + +change [index v] by (1) + +change [even total v] by (letter (index) of (credit card number)) + +change [index v] by (1) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-credit-card/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-credit-card/scratch-hints.md new file mode 100644 index 000000000..251091e6d --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-credit-card/scratch-hints.md @@ -0,0 +1,13 @@ +- Every second number (the odd digits of the credit card, i.e. 1st, 3rd, + 5th, and so on digits) is multiplied by 2, but if this gives a 2-digit + number you should subtract 9 from it. + For example, if the input digit 8 is multiplied by 2, that gives 16, + and subtracting 9 from that gives 7 to add to the running total + (some descriptions of the method say that you should add the two digits + together, which gives the same result e.g. for 8x2 = 16 adding the digits + in the result is 1+6, which is also 7). + +- You then add the sum of these values to the sum of the even digits of the + credit card. + The credit card number is valid if the final total is a multiple of 10 + (i.e. total mod 10 is equal to 0). diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-credit-card/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-credit-card/scratch-solution.md new file mode 100644 index 000000000..2942f0863 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-credit-card/scratch-solution.md @@ -0,0 +1,25 @@ +```scratch +when green flag clicked +set [index v] to [1] +set [credit card number v] to [0] +set [even total v] to [0] +set [odd total v] to [0] +ask [Enter a credit card number:] and wait +change [credit card number v] by (answer) +repeat (8) + if <((letter (index) of (credit card number)) * (2)) < [10]> then + change [odd total v] by ((letter (index) of (credit card number)) * (2)) + change [index v] by (1) + else + change [odd total v] by (((letter (index) of (credit card number)) * (2)) - (9)) + change [index v] by (1) + end + change [even total v] by (letter (index) of (credit card number)) + change [index v] by (1) +end +if <(((even total) + (odd total)) mod (10)) = [0]> then + say [Valid credit card number!] +else + say [Invalid credit card number!] +end +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-isbn-10/check-valid-isbn-10.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-isbn-10/check-valid-isbn-10.md new file mode 100644 index 000000000..e0af998ff --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-isbn-10/check-valid-isbn-10.md @@ -0,0 +1,24 @@ +# Check for valid ISBN-10 number + +## Requirement: + +Write a program that asks the user to enter an ISBN-10 number as an input +and says if the ISBN-10 number is valid or not. +The last digit of the ISBN-10 must range from 0 to 10 (the symbol X is +used for 10). + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +| ---------- | ---------------------------------- | +| 0201530821 | This is a valid ISBN-10 number! | +| 0140032193 | This is a valid ISBN-10 number! | +| 0521357411 | This is a valid ISBN-10 number! | +| 043942089X | This is a valid ISBN-10 number! | +| 0201540821 | This is an invalid ISBN-10 number! | +| 0240032193 | This is an invalid ISBN-10 number! | +| 0521357511 | This is an invalid ISBN-10 number! | +| 0439420896 | This is an invalid ISBN-10 number! | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-isbn-10/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-isbn-10/scratch-expected.md new file mode 100644 index 000000000..5b8f21fdc --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-isbn-10/scratch-expected.md @@ -0,0 +1,51 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148477875/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Enter an ISBN-10 number:] and wait +``` + +```scratch:split:random +say [This is a valid ISBN-10 number!] + +say [This is an invalid ISBN-10 number!] +``` + +```scratch:split:random +change [total v] by ((multiplier) * (letter (index) of (ISBN-10 number))) + +change [multiplier v] by (-1) + +change [index v] by (1) + +set [index v] to [1] + +set [multiplier v] to [10] + +set [total v] to [0] + +change [total v] by ((multiplier) * (10)) + +set [ISBN-10 number v] to (answer) +``` + +```scratch:split:random +if <((total) mod (11)) = [0]> then +else +end + +repeat (10) +end + +if <<(index) = [10]> and <(letter (index) of (ISBN-10 number)) = [X]>> then +else +end +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-isbn-10/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-isbn-10/scratch-hints.md new file mode 100644 index 000000000..5f8b55803 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-isbn-10/scratch-hints.md @@ -0,0 +1,14 @@ +- To check if an ISBN-10 number is valid, the sum of all the ten digits + each multiplied by its (integer) weight, descending from 10 to 1, needs + to be a multiple of 11 (i.e. total mod 11 is equal to 0). + +- If the last digit of an ISBN-10 is X, you need to multiply 10 by its + integer weight 1 (as it’s the last digit) to calculate the total. + + For example for an ISBN-10 of 0201530821: + + \[ total=(100)+(92)+(80)+(71)+(65)+(53)+(40)+(38)+(22)+(11) \] + + \[ total=0+18+0+7+30+15+0+24+4+1=99 \] + + This is a valid ISBN-10 as 99 mod 11 is 0. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-isbn-10/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-isbn-10/scratch-solution.md new file mode 100644 index 000000000..bf0b4a09c --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/check-valid-isbn-10/scratch-solution.md @@ -0,0 +1,22 @@ +```scratch +when green flag clicked +set [index v] to [1] +set [multiplier v] to [10] +set [total v] to [0] +ask [Enter an ISBN-10 number:] and wait +set [ISBN-10 number v] to (answer) +repeat (10) + if <<(index) = [10]> and <(letter (index) of (ISBN-10 number)) = [X]>> then + change [total v] by ((multiplier) * (10)) + else + change [total v] by ((multiplier) * (letter (index) of (ISBN-10 number))) + change [multiplier v] by (-1) + change [index v] by (1) + end +end +if <((total) mod (11)) = [0]> then + say [This is a valid ISBN-10 number!] +else + say [This is an invalid ISBN-10 number!] +end +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-colour-input/count-black-squares-one-colour-input.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-colour-input/count-black-squares-one-colour-input.md new file mode 100644 index 000000000..69f7b86e3 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-colour-input/count-black-squares-one-colour-input.md @@ -0,0 +1,18 @@ +# Count the black squares (one colour per input) + +## Requirement: + +Create a program which asks the user to enter 5 black or white squares (B for +black or W for white) one at a time and outputs the total number of black +squares. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +| --------------------- | ------ | +| B
W
W
B
W | 2 | +| B
B
B
B
B | 5 | +| W
W
W
W
W | 0 | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-colour-input/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-colour-input/scratch-expected.md new file mode 100644 index 000000000..c81db6c63 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-colour-input/scratch-expected.md @@ -0,0 +1,30 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148425996/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Please enter a black or white square (B for black or W for white):] and wait + +say (black cards total) +``` + +```scratch:split:random +set [black cards total v] to [0] + +change [black cards total v] by (1) +``` + +```scratch:split:random +repeat (5) +end + +if <(answer) = [B]> then +end +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-colour-input/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-colour-input/scratch-hints.md new file mode 100644 index 000000000..6deb53b36 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-colour-input/scratch-hints.md @@ -0,0 +1,10 @@ +- To store the total number of black cards, create a variable called “black + cards total”. + You need to increase the value of this variable every time the user enters + a black card. + Display the value of “black cards total” at the end. +- Use the `scratch:repeat ()` block to run the blocks inside a specified + number of times. + In this challenge you need to repeat the blocks 5 times. +- The `scratch:if <> then` block checks if the condition is true and then + runs the blocks inside of the IF block. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-colour-input/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-colour-input/scratch-solution.md new file mode 100644 index 000000000..095f483ef --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-colour-input/scratch-solution.md @@ -0,0 +1,11 @@ +```scratch +when green flag clicked +set [black cards total v] to [0] +repeat (5) + ask [Please enter a black or white square (B for black or W for white):] and wait + if <(answer) = [B]> then + change [black cards total v] by (1) + end +end +say (black cards total) +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-line-input/count-black-squares-one-line-input.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-line-input/count-black-squares-one-line-input.md new file mode 100644 index 000000000..b6c51c17e --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-line-input/count-black-squares-one-line-input.md @@ -0,0 +1,18 @@ +# Count the black squares (one line of input) + +## Requirement: + +Create a program which asks the user to enter a series of black and white +squares in one line of input (B for black and W for white) and returns the +number of black squares as the output. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +| ------- | ------ | +| WWBBWBB | 4 | +| WWW | 0 | +| B | 1 | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-line-input/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-line-input/scratch-expected.md new file mode 100644 index 000000000..ce1d27b90 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-line-input/scratch-expected.md @@ -0,0 +1,38 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148426017/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +say (black cards total) + +ask [Please enter a sequence of black and white squares (B for black and W for white):] and wait +``` + +```scratch:split:random +change [index v] by (1) + +change [black cards total v] by (1) +``` + +```scratch:split:random +repeat (length of (cards)) +end + +if <(letter (index) of (cards)) = [B]> then +end +``` + +```scratch:split:random +set [black cards total v] to [0] + +set [index v] to [1] + +set [cards v] to (answer) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-line-input/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-line-input/scratch-hints.md new file mode 100644 index 000000000..688323c58 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-line-input/scratch-hints.md @@ -0,0 +1,16 @@ +- Make a variable called “black cards total” and set its value to 0. + Make a string variable called “cards” and set its value to the input + entered by the end user. + Check each letter of the string and if it’s ‘B’ add 1 to the + “black cards total”. + Display the “black cards total” as the output. + +- You can access a letter at the specified position in a string by using + the `scratch:letter (1) of [world]` block under “Operators”. + For example: `scratch:letter (1) of [world] //w` + +- In this challenge you need to access all the letters in user’s input and + check to see if each of them is equal to B (black). + +- You can find how many letters a string has by using the + `scratch:length of [world]` block unders “Operators”. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-line-input/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-line-input/scratch-solution.md new file mode 100644 index 000000000..435455ac8 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/count-black-squares-one-line-input/scratch-solution.md @@ -0,0 +1,14 @@ +```scratch +when green flag clicked +set [black cards total v] to [0] +set [index v] to [1] +ask [Please enter a sequence of black and white squares (B for black and W for white):] and wait +set [cards v] to (answer) +repeat (length of (cards)) + if <(letter (index) of (cards)) = [B]> then + change [black cards total v] by (1) + end + change [index v] by (1) +end +say (black cards total) +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-after-input/detect-parity-error-in-any-rows-after-input.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-after-input/detect-parity-error-in-any-rows-after-input.md new file mode 100644 index 000000000..3080a9c95 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-after-input/detect-parity-error-in-any-rows-after-input.md @@ -0,0 +1,19 @@ +# Detect parity error in any number of rows (after all the rows are entered) + +## Requirement: + +Write a program that asks the user how many rows they would like to enter then +asks for the rows one row at a time. +It then displays the rows with parity error at the end as the output. +You will need to use a list to store all the rows for this challenge +(store each row as an item of the list). + +## Testing examples: + +Your program should display the outputs shown in these panels for the given +inputs provided: + +| Input | Output | +| ------- | ------ | +| How many rows would you like to enter? **6**
Enter 6 cards for row 1: **WBBWWW**
Enter 6 cards for row 2: **BBWBWB**
Enter 6 cards for row 3: **BWWWBW**
Enter 6 cards for row 4: **BBBBBB**
Enter 6 cards for row 5: **WBBWWB**
Enter 6 cards for row 6: **BBBWBB** | There is a parity error in row 5
There is a parity error in row 6 | +| How many rows would you like to enter? **4**
Enter 4 cards for row 1: **BWWB**
Enter 4 cards for row 2: **BBBB**
Enter 4 cards for row 3: **WBBB**
Enter 4 cards for row 4: **WBWB** | There is parity error in row 3 | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-after-input/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-after-input/scratch-expected.md new file mode 100644 index 000000000..6636b92ce --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-after-input/scratch-expected.md @@ -0,0 +1,65 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148426175/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +say (join [There is a parity error in row ] (index row)) for (3) secs + +add (answer) to [rows v] + +delete (all v) of [rows v] +``` + +```scratch:split:random +set [black cards total v] to [0] + +set [row number v] to [1] + +set [number of rows v] to (answer) + +change [row number v] by (1) + +set [index row v] to [1] + +set [index v] to [1] + +change [black cards total v] by (1) + +change [index v] by (1) + +set [black cards total v] to [0] + +change [index row v] by (1) + +change [black cards total v] by (1) +``` + +```scratch:split:random +ask [How many rows would you like to enter?] and wait + +ask (join [Enter row ] (row number)) and wait +``` + +```scratch:split:random +repeat (number of rows) +end + +repeat (length of [rows v] :: list) +end + +repeat (length of (item (index row) of [rows v] :: list)) +end + +if <((black cards total) mod (2)) = [1]> then +end + +if <(letter (index) of (item (index row) of [rows v] :: list)) = [B]> then +end +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-after-input/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-after-input/scratch-hints.md new file mode 100644 index 000000000..7e071e6aa --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-after-input/scratch-hints.md @@ -0,0 +1,43 @@ +- For this challenge you need to use a list to store all the rows (each + item in the list is a row). + You then need to go through each row (item of your list) and check if + they have a parity error (i.e. the total number of black cards in + each row is odd). + If one or more rows have parity errors, you then need to find their + positions in the list and report these positions as the output. + +- To make a new list select “Make a list” under “Data” script. + +- Add items to your list by using the `scratch:add () to [rows v]` block. + +- You can access an item in a specific position in your list by using the + `scratch:item () of [rows v] :: list` block. + +- The `scratch:length of [rows v] :: list` block reports how many items are + in the list. + +- You need to delete all the previous items from your list at the start of + your program. + If you don’t do this, your new items get added to the list every time you + run your program. + To delete all the items from your list select “all” from the drop down + list on the `scratch:delete (all v) of [rows v]` block. + +- You can access a letter at the specified position in a string by using + the `scratch:letter (1) of [world]` block under “Operators”. + For example: `scratch:letter (1) of [world] //w` + +- In this challenge you need to access all the letters in user’s input + (each row of the parity trick) and check to see how many of them + are equal to B (black). + Store the total number of black squares in a variable called + “black cards total”. + +- You can find how many letters a string has by using the + `scratch:length of [world]` block unders “Operators”. + +- To find out if a number is even or odd, use the `scratch:() mod ()` block + (under "Operators") to find the remainder after dividing that number by + two. + If the remainder is zero the number is even. + For example: `scratch:(37) mod (10) //7` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-after-input/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-after-input/scratch-solution.md new file mode 100644 index 000000000..0188b7308 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-after-input/scratch-solution.md @@ -0,0 +1,28 @@ +```scratch +when green flag clicked +delete (all v) of [rows v] +set [black cards total v] to [0] +set [row number v] to [1] +ask [How many rows would you like to enter?] and wait +set [number of rows v] to (answer) +repeat (number of rows) + ask (join [Enter row ] (row number)) and wait + add (answer) to [rows v] + change [row number v] by (1) +end +set [index row v] to [1] +repeat (length of [rows v] :: list) + set [index v] to [1] + repeat (length of (item (index row) of [rows v] :: list)) + if <(letter (index) of (item (index row) of [rows v] :: list)) = [B]> then + change [black cards total v] by (1) + end + change [index v] by (1) + end + if <((black cards total) mod (2)) = [1]> then + say (join [There is a parity error in row ] (index row)) for (3) secs + end + set [black cards total v] to [0] + change [index row v] by (1) +end +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-or-columns/detect-parity-error-in-any-rows-or-columns.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-or-columns/detect-parity-error-in-any-rows-or-columns.md new file mode 100644 index 000000000..a06a14770 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-or-columns/detect-parity-error-in-any-rows-or-columns.md @@ -0,0 +1,20 @@ +# Detect parity error in rows and columns (any length) + +## Requirement: + +Write a program that asks the user to enter the number of rows for a parity +trick followed by the rows (one row at a time) as the input and says +which row and column has a parity error as the output. +We are assuming that only one card has been flipped over (i.e. there is only +one row and one column with error). +You will need to use a list to store the rows for this challenge. + +## Testing examples: + +Your program should display the outputs shown in these panels for the given +inputs provided: + +| Input | Output | +| ------- | ------ | +| How many rows would you like to enter? **6**
Enter 6 cards for row 1: **WBBWWW**
Enter 6 cards for row 2: **BBWBWB**
Enter 6 cards for row 3: **BWWWBW**
Enter 6 cards for row 4: **BBBBBB**
Enter 6 cards for row 5: **WBBWWB**
Enter 6 cards for row 6: **BBBWBB** | There is a parity error in row 5 and column 2. | +| How many rows would you like to enter? **4**
Enter 4 cards for row 1: **BWWB**
Enter 4 cards for row 2: **BBBB**
Enter 4 cards for row 3: **WBBB**
Enter 4 cards for row 4: **WBWB** | There is a parity error in row 3 and column 2. | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-or-columns/extra-challenge.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-or-columns/extra-challenge.md new file mode 100644 index 000000000..22ff75b7e --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-or-columns/extra-challenge.md @@ -0,0 +1,3 @@ +For the extra challenge you need to check if the number of cards in each row +(number of columns) is equal the total number of rows +(i.e. if you have 6 rows each row must contain 6 cards). diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-or-columns/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-or-columns/scratch-expected.md new file mode 100644 index 000000000..5fb4deb51 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-or-columns/scratch-expected.md @@ -0,0 +1,89 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148426200/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +delete (all v) of [cards v] + +add (row) to [cards v] + +say (join (join (join [There is a parity error in row ] (error in row)) [ and column ]) (error in column)) +``` + +```scratch:split:random +change [black cards total v] by (1) + +change [row index v] by (1) + +set [error in column v] to (index) + +change [index v] by (1) + +set [black cards total v] to [0] + +set [row index v] to [1] + +set [index v] to [1] + +change [row number v] by (1) + +set [error in row v] to (row number) + +change [index v] by (1) + +set [black cards total v] to [0] + +set [index v] to [1] + +set [row number v] to [1] + +set [error in row v] to [0] + +set [error in column v] to [0] + +set [number of rows v] to (answer) + +set [row v] to (answer) + +change [black cards total v] by (1) +``` + +```scratch:split:random +ask (join (join (join [Enter ] (number of rows)) [ cards for row ]) (row number)) and wait + +ask [How many rows would you like to enter?] and wait +``` + +```scratch:split:random +repeat (number of rows) +end + +repeat (length of (row)) +end + +if <(letter (index) of (row)) = [B]> then +end + +if <((black cards total) mod (2)) = [1]> then +end + +repeat (number of rows) +end + +repeat (number of rows) +end + +if <(letter (index) of (item (row index) of [cards v] :: list)) = [B]> then +end + +if <((black cards total) mod (2)) = [1]> then +end + +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-or-columns/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-or-columns/scratch-hints.md new file mode 100644 index 000000000..c3088edc1 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-or-columns/scratch-hints.md @@ -0,0 +1,54 @@ +- For this challenge you need to use a list to store all the rows (each + item in the list is a row). + You then need to go through each item of your list and check if they have + an error (i.e. the total number of black cards in each row are odd). + If there is a parity error in any of the rows you then need to store its + position in the list in a variable called “error in row”. + +- You then need to check if there are any parity errors in any of the + columns. + To check this for the first column you need to go through the first + letters of all the items in your list and check if the total number of + black cards are odd (i.e. there is a parity error in that column). + You then need to repeat this for all the other columns. + If you find a parity error in any of the columns store it in a variable + called “error in column” + +- Display the values for “error in row” and “error in column” at the end + of your program as the output. + +- To make a new list select “Make a list” under “Data” script. + +- Add items to your list by using the `scratch:add () to [cards v]` block. + +- You can access an item in a specific position in your list by using the + `scratch:item () of [cards v] :: list` block. + +- The `scratch:length of [cards v] :: list` block reports how many items are + in the list. + +- You need to delete all the previous items from your list at the start of + your program. + If you don’t do this, your new items get added to the list every time you + run your program. + To delete all the items from your list select “all” from the drop down + list on the `scratch:delete (all v) of [cards v]` block. + +- You can access a letter at the specified position in a string by using + the `scratch:letter (1) of [world]` block under “Operators”. + For example: `scratch:letter (1) of [world] //w` + +- In this challenge you need to access all the letters in user’s input + (each row of the parity trick) and check to see how many of them + are equal to B (black). + Store the total number of black squares in a variable called + “black cards total”. + +- You can find how many letters a string has by using the + `scratch:length of [world]` block unders “Operators”. + +- To find out if a number is even or odd, use the `scratch:() mod ()` block + (under "Operators") to find the remainder after dividing that number by + two. + If the remainder is zero the number is even. + For example: `scratch:(37) mod (10) //7` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-or-columns/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-or-columns/scratch-solution.md new file mode 100644 index 000000000..d3869ed8c --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows-or-columns/scratch-solution.md @@ -0,0 +1,42 @@ +```scratch +when green flag clicked +set [row number v] to [1] +set [error in row v] to [0] +set [error in column v] to [0] +delete (all v) of [cards v] +ask [How many rows would you like to enter?] and wait +set [number of rows v] to (answer) +repeat (number of rows) + ask (join (join (join [Enter ] (number of rows)) [ cards for row ]) (row number)) and wait + set [row v] to (answer) + add (row) to [cards v] + set [black cards total v] to [0] + set [index v] to [1] + repeat (length of (row)) + if <(letter (index) of (row)) = [B]> then + change [black cards total v] by (1) + end + change [index v] by (1) + end + if <((black cards total) mod (2)) = [1]> then + set [error in row v] to (row number) + end + change [row number v] by (1) +end +set [index v] to [1] +repeat (number of rows) + set [black cards total v] to [0] + set [row index v] to [1] + repeat (number of rows) + if <(letter (index) of (item (row index) of [cards v] :: list)) = [B]> then + change [black cards total v] by (1) + end + change [row index v] by (1) + end + if <((black cards total) mod (2)) = [1]> then + set [error in column v] to (index) + end + change [index v] by (1) +end +say (join (join (join [There is a parity error in row ] (error in row)) [ and column ]) (error in column)) +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows/detect-parity-error-in-any-rows.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows/detect-parity-error-in-any-rows.md new file mode 100644 index 000000000..314370e4b --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows/detect-parity-error-in-any-rows.md @@ -0,0 +1,17 @@ +# Detect parity error in any number of rows (after each row is entered) + +## Requirement: + +Write a program which asks the user to enter any number of rows and checks if +there is a parity error in any of the rows after entering each row. + +## Testing examples: + +Your program should display the outputs shown in these panels for the given +inputs provided: + +| Input | Output | +| ----- | --------------------------------- | +| WWW | Row 1 is ok! | +| WBW | There is a parity error in row 2! | +| WBB | Row 3 is ok! | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows/scratch-expected.md new file mode 100644 index 000000000..fd6f4f666 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows/scratch-expected.md @@ -0,0 +1,59 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148426139/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked +``` + +```scratch:split:random +ask [How many rows would you like to enter?] and wait + +ask (join (join (join [Please enter ] (number of rows)) [ cards for row ]) (row number)) and wait +``` + +```scratch:split:random +set [black cards total v] to [0] + +set [row number v] to [1] + +set [index v] to [1] + +set [number of rows v] to (answer) + +set [row v] to (answer) + +change [black cards total v] by (1) + +change [index v] by (1) + +change [row number v] by (1) + +set [black cards total v] to [0] +``` + +```scratch:split:random +say (join [There is a parity error is row ] (row number)) for (2) secs + +say (join (join [Row ] (row number)) [ is OK!]) for (2) secs +``` + +```scratch:split:random +if <(letter (index) of (row)) = [B]> then +end + +repeat (number of rows) +end + +repeat (length of (row)) +end + +if <((black cards total) mod (2)) = [0]> then +else +end +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows/scratch-hints.md new file mode 100644 index 000000000..0ff387303 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows/scratch-hints.md @@ -0,0 +1,31 @@ +- Make a variable called “number of rows” and set its value to the input + entered by the end user. + Make a variable called “black cards total” and set its value to 0. + Also make a string variable called “row” and set its value to the input + entered by the end user. + Check each letter of the string and if it’s ‘B’ add 1 to the “black + cards total”. + If the value of the “black cards total” is even (after checking every + card in the row), display a message that the parity row is ok. + If the value of the “black cards total” is odd, display a message that + the row has a parity error. + Repeat this “number of rows” times (for each row). + +- You can access a letter at the specified position in a string by using + the `scratch:letter (1) of [world]` block under “Operators”. + For example: `scratch:letter (1) of [world] //w` + +- In this challenge you need to access all the letters in user’s input + (each row of the parity trick) and check to see how many of them + are equal to B (black). + Store the total number of black squares in a variable called “black + cards total”. + +- You can find how many letters a string has by using the + `scratch:length of [world]` block unders “Operators”. + +- To find out if a number is even or odd, use the `scratch:() mod ()` block + (under "Operators") to find the remainder after dividing that number by + two. + If the remainder is zero the number is even. + For example: `scratch:(37) mod (10) //7` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows/scratch-solution.md new file mode 100644 index 000000000..b2d762ebe --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-any-rows/scratch-solution.md @@ -0,0 +1,25 @@ +```scratch +when green flag clicked +set [black cards total v] to [0] +set [row number v] to [1] +ask [How many rows would you like to enter?] and wait +set [number of rows v] to (answer) +repeat (number of rows) + set [index v] to [1] + ask (join (join (join [Please enter ] (number of rows)) [ cards for row ]) (row number)) and wait + set [row v] to (answer) + repeat (length of (row)) + if <(letter (index) of (row)) = [B]> then + change [black cards total v] by (1) + end + change [index v] by (1) + end + if <((black cards total) mod (2)) = [0]> then + say (join (join [Row ] (row number)) [ is OK!]) for (2) secs + else + say (join [There is a parity error is row ] (row number)) for (2) secs + end + change [row number v] by (1) + set [black cards total v] to [0] +end +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-row/detect-parity-error-in-row.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-row/detect-parity-error-in-row.md new file mode 100644 index 000000000..051c4b2cf --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-row/detect-parity-error-in-row.md @@ -0,0 +1,18 @@ +# Detect parity error in a row + +## Requirement: + +Write a program which asks the user to enter a row of black and white cards +and checks if there is a parity error in that row +(i.e. you should check if there is an odd number of black squares in that row). + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +| ------ | ------------------------------------- | +| WWWBBW | There is no parity error in this row! | +| WBBWBW | There is a parity error in this row! | +| WWWWWW | There is no parity error in this row! | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-row/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-row/scratch-expected.md new file mode 100644 index 000000000..3614c0f14 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-row/scratch-expected.md @@ -0,0 +1,47 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148426097/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Please enter a row of black and white cards (B for black and W for white):] and wait +``` + + +```scratch:split:random +if <(letter (index) of (cards)) = [B]> then +end + +repeat (length of (cards)) +end + +if <((black cards total) mod (2)) = [0]> then +else +end +``` + +```scratch:split:random +say [There is no parity error in this row!] + +say [There is a parity error in this row!] +``` + +```scratch:split:random +change [black cards total v] by (1) + +change [index v] by (1) +``` + +```scratch:split:random +set [black cards total v] to [0] + +set [index v] to [1] + +set [cards v] to (answer) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-row/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-row/scratch-hints.md new file mode 100644 index 000000000..17f94a9b0 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-row/scratch-hints.md @@ -0,0 +1,17 @@ +- You can access a letter at the specified position in a string by using + the `scratch:letter (1) of [world]` block under “Operators”. + For example: `scratch:letter (1) of [world] //w` + +- In this challenge you need to access all the letters in user’s input and + check to see how many of them are equal to B (black). + Store the total number of black squares in a variable called “black + cards total”. + +- You can find how many letters a string has by using the + `scratch:length of [world]` block unders “Operators”. + +- To find out if a number is even or odd, use the `scratch:() mod ()` block + (under "Operators") to find the remainder after dividing that number by + two. + If the remainder is zero the number is even. + For example: `scratch:(37) mod (10) //7` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-row/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-row/scratch-solution.md new file mode 100644 index 000000000..e96b5fc6e --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-row/scratch-solution.md @@ -0,0 +1,18 @@ +```scratch +when green flag clicked +set [black cards total v] to [0] +set [index v] to [1] +ask [Please enter a row of black and white cards (B for black and W for white):] and wait +set [cards v] to (answer) +repeat (length of (cards)) + if <(letter (index) of (cards)) = [B]> then + change [black cards total v] by (1) + end + change [index v] by (1) +end +if <((black cards total) mod (2)) = [0]> then + say [There is no parity error in this row!] +else + say [There is a parity error in this row!] +end +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-six-rows/detect-parity-error-in-six-rows.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-six-rows/detect-parity-error-in-six-rows.md new file mode 100644 index 000000000..143149578 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-six-rows/detect-parity-error-in-six-rows.md @@ -0,0 +1,19 @@ +# Detect parity error in 6 rows (after each row is entered) + +## Requirement: + +Write a program that asks the user to enter 6 rows of black and white cards (6 cards in each row) and checks if there is a parity error in any of the rows after entering each row. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +| ------ | --------------------------------- | +| WWWWWW | Row 1 is ok! | +| BBWWBB | Row 2 is ok! | +| BBBWWW | There is a parity error in row 3! | +| BBBBBB | Row 4 is ok! | +| WBBWBB | Row 5 is ok! | +| WBWBWB | There is a parity error in row 6! | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-six-rows/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-six-rows/scratch-expected.md new file mode 100644 index 000000000..229dfa4f4 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-six-rows/scratch-expected.md @@ -0,0 +1,56 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148426115/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask (join [Please enter 6 black or white cards for row ] (row number)) and wait +``` + + +```scratch:split:random +repeat (6) +end + +repeat (6) +end + +if <(letter (index) of (row)) = [B]> then +end + +if <((black cards total) mod (2)) = [0]> then +else +end +``` + +```scratch:split:random +say (join (join [Row ] (row number)) [ is OK!]) for (2) secs + +say (join [There is a parity error in row ] (row number)) for (2) secs +``` + +```scratch:split:random +set [row v] to [] + +set [black cards total v] to [0] + +set [row number v] to [1] + +change [row number v] by (1) + +set [black cards total v] to [0] + +change [index v] by (1) + +change [black cards total v] by (1) + +set [row v] to (answer) + +set [index v] to [1] +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-six-rows/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-six-rows/scratch-hints.md new file mode 100644 index 000000000..4097aa983 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-six-rows/scratch-hints.md @@ -0,0 +1,29 @@ +- Make a variable called “black cards total” and set its value to 0. + Make a string variable called “row” and set its value to the input entered + by the end user. + Check each letter of the string and if it’s ‘B’ add 1 to the “black cards + total”. + If the value of the “black cards total” is even (after checking every card + in the row), display a message that the parity row is ok. + If the value of the “black cards total” is odd, display a message that + the row has a parity error. + Repeat this 6 times (for each row). + +- You can access a letter at the specified position in a string by using + the `scratch:letter (1) of [world]` block under “Operators”. + For example: `scratch:letter (1) of [world] //w` + +- In this challenge you need to access all the letters in user’s input + (each row of the parity trick) and check to see how many of them + are equal to B (black). + Store the total number of black squares in a variable called “black + cards total”. + +- You can find how many letters a string has by using the + `scratch:length of [world]` block unders “Operators”. + +- To find out if a number is even or odd, use the `scratch:() mod ()` block + (under "Operators") to find the remainder after dividing that number by + two. + If the remainder is zero the number is even. + For example: `scratch:(37) mod (10) //7` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-six-rows/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-six-rows/scratch-solution.md new file mode 100644 index 000000000..996202cb7 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/detect-parity-error-in-six-rows/scratch-solution.md @@ -0,0 +1,24 @@ +```scratch +when green flag clicked +set [row v] to [] +set [black cards total v] to [0] +set [row number v] to [1] +repeat (6) + ask (join [Please enter 6 black or white cards for row ] (row number)) and wait + set [row v] to (answer) + set [index v] to [1] + repeat (6) + if <(letter (index) of (row)) = [B]> then + change [black cards total v] by (1) + end + change [index v] by (1) + end + if <((black cards total) mod (2)) = [0]> then + say (join (join [Row ] (row number)) [ is OK!]) for (2) secs + else + say (join [There is a parity error in row ] (row number)) for (2) secs + end + change [row number v] by (1) + set [black cards total v] to [0] +end +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-at-a-time/product-code-12-last-digit-one-at-a-time.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-at-a-time/product-code-12-last-digit-one-at-a-time.md new file mode 100644 index 000000000..ef370235f --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-at-a-time/product-code-12-last-digit-one-at-a-time.md @@ -0,0 +1,17 @@ +# 12 digit product codes: Calculate the last digit (entered one digit at a time) + +## Requirement: + +Write a program that asks the user to enter the first 11 digits of a 12 +digit product code, one at a time, and works out the last digit as the output. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +| --------------------------------------------------- | -------------------- | +| 6
7
1
8
6
0
0
1
3
6
2 | The last digit is: 4 | +| 8
3
8
3
1
0
0
2
2
5
2 | The last digit is: 4 | +| 0
9
3
6
1
6
0
1
6
0
5 | The last digit is: 3 | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-at-a-time/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-at-a-time/scratch-expected.md new file mode 100644 index 000000000..e240040d4 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-at-a-time/scratch-expected.md @@ -0,0 +1,50 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148477095/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +say (join [The last digit of the product code is ] (last digit)) + +repeat (5) +end +``` + + +```scratch:split:random +ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait + +ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait + +ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait +``` + +```scratch:split:random +set [total 1 v] to [0] + +set [total 2 v] to [0] + +set [index v] to [1] + +set [last digit v] to [0] + +change [index v] by (1) + +change [total 1 v] by (answer) + +change [index v] by (1) + +change [total 2 v] by (answer) + +change [index v] by (1) + +change [total 1 v] by (answer) + +set [last digit v] to (((0) - (((total 1) * (3)) + (total 2))) mod (10)) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-at-a-time/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-at-a-time/scratch-hints.md new file mode 100644 index 000000000..b5b9a71df --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-at-a-time/scratch-hints.md @@ -0,0 +1,6 @@ +- You need to add up 1st, 3rd, 5th and so on digits and store the result + in a variable called "total 1". + Then add up 2nd, 4th, 6th and so on digits and store the result in + variable called "total 2". + The last digit (check digit) is then calculated by subtracting the sum + of "total 1" multiplied by 3, and "total 2" from 0 mod 10. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-at-a-time/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-at-a-time/scratch-solution.md new file mode 100644 index 000000000..0898a7ddf --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-at-a-time/scratch-solution.md @@ -0,0 +1,20 @@ +```scratch +when green flag clicked +set [total 1 v] to [0] +set [total 2 v] to [0] +set [index v] to [1] +set [last digit v] to [0] +repeat (5) + ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait + change [index v] by (1) + change [total 1 v] by (answer) + ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait + change [index v] by (1) + change [total 2 v] by (answer) +end +ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait +change [index v] by (1) +change [total 1 v] by (answer) +set [last digit v] to (((0) - (((total 1) * (3)) + (total 2))) mod (10)) +say (join [The last digit of the product code is ] (last digit)) +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-line/product-code-12-last-digit-one-line.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-line/product-code-12-last-digit-one-line.md new file mode 100644 index 000000000..87a318ce7 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-line/product-code-12-last-digit-one-line.md @@ -0,0 +1,21 @@ +# 12 digit product codes: Calculate the last digit (one line of input) + +## Requirement: + +Write a program that asks the user to enter the first 11 digits of a +product code, in one line of input, and works out the last digit +as the output. +For this challenge make sure the user can only enter a 12 digit +product code (the first 11 digits). + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +| ----------- | -------------------- | +| 83831002252 | The last digit is: 4 | +| 81157101357 | The last digit is: 9 | +| 61414100001 | The last digit is: 2 | +| 69277101744 | The last digit is: 0 | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-line/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-line/scratch-expected.md new file mode 100644 index 000000000..7b9d47a8b --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-line/scratch-expected.md @@ -0,0 +1,57 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148477141/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Enter first 11 digits of the product code:] and wait +``` + +```scratch:split:random +say (join [The last digit of the product code is: ] (last digit)) + +say [You must enter a 11 digit number!] +``` + +```scratch:split:random +repeat (5) +end + +if <(length of (first 11 digits)) = [11]> then +else +end +``` + +```scratch:split:random +set [index v] to [1] + +set [first 11 digits v] to [0] + +set [total 1 v] to [0] + +set [total 2 v] to [0] + +set [last digit v] to [0] + +set [total 1 v] to ((total 1) + (letter (index) of (first 11 digits))) + +change [index v] by (1) + +set [last digit v] to (((0) - (((total 1) * (3)) + (total 2))) mod (10)) + +set [total 1 v] to ((total 1) + (letter (index) of (first 11 digits))) + +change [index v] by (1) + +set [total 2 v] to ((total 2) + (letter (index) of (first 11 digits))) + +change [index v] by (1) + +set [first 11 digits v] to (answer) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-line/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-line/scratch-hints.md new file mode 100644 index 000000000..07daf2536 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-line/scratch-hints.md @@ -0,0 +1,10 @@ +- For the 11 digits entered, you need to add up 1st, 3rd, 5th and so on + digits and store the result in a variable called total 1. + Then add up 2nd, 4th, 6th and so on digits and store the result in + variable called total 2. + The last digit (check digit) is then calculated by subtracting the sum + of total 1 multiplied by 3 and total 2 from 0 mod 10. + +- You can access a letter (or a digit) at a specified position in a string + (or number) by using the `scratch:letter () of []` block under “Operators”. + For example: `scratch:letter (4) of [18392819202910] //9` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-line/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-line/scratch-solution.md new file mode 100644 index 000000000..547a70eb0 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-last-digit-one-line/scratch-solution.md @@ -0,0 +1,24 @@ +```scratch +when green flag clicked +set [index v] to [1] +set [first 11 digits v] to [0] +set [total 1 v] to [0] +set [total 2 v] to [0] +set [last digit v] to [0] +ask [Enter first 11 digits of the product code:] and wait +set [first 11 digits v] to (answer) +if <(length of (first 11 digits)) = [11]> then + repeat (5) + set [total 1 v] to ((total 1) + (letter (index) of (first 11 digits))) + change [index v] by (1) + set [total 2 v] to ((total 2) + (letter (index) of (first 11 digits))) + change [index v] by (1) + end + set [total 1 v] to ((total 1) + (letter (index) of (first 11 digits))) + change [index v] by (1) + set [last digit v] to (((0) - (((total 1) * (3)) + (total 2))) mod (10)) + say (join [The last digit of the product code is: ] (last digit)) +else + say [You must enter a 11 digit number!] +end +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-at-a-time/product-code-12-multiple-10-one-at-a-time.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-at-a-time/product-code-12-multiple-10-one-at-a-time.md new file mode 100644 index 000000000..c6e2f688c --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-at-a-time/product-code-12-multiple-10-one-at-a-time.md @@ -0,0 +1,62 @@ +# 12 digit product codes: Check if total is a multiple of 10 (entered one at a time) + +## Requirement: + +Write a program that asks the user to enter a 12 digit product code, +entering each digit one at a time. +Then adds up all the digits, every second one multiplied by 3 (1st, 3rd, +and so on) and show if this total is a multiple of 10 or not. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +{panel type="general" title="Example"} + +**Input** +``` +Enter digit 1 of the product code: +8 + +Enter digit 2 of the product code: +3 + +Enter digit 3 of the product code: +8 + +Enter digit 4 of the product code: +3 + +Enter digit 5 of the product code: +1 + +Enter digit 6 of the product code: +0 + +Enter digit 7 of the product code: +0 + +Enter digit 8 of the product code: +2 + +Enter digit 9 of the product code: +2 + +Enter digit 10 of the product code: +5 + +Enter digit 11 of the product code: +2 + +Enter digit 12 of the product code: +4 +``` + +**Output** + +``` +The sum is a multiple of 10 +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-at-a-time/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-at-a-time/scratch-expected.md new file mode 100644 index 000000000..927c38273 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-at-a-time/scratch-expected.md @@ -0,0 +1,44 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148476891/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +say (join [Total: ] (total)) +``` + + +```scratch:split:random +repeat (6) +end +``` + +```scratch:split:random +ask (join (join [Enter digit ] (counter)) [ of the product code:]) and wait + +ask (join (join [Enter digit ] (counter)) [ of the product code:]) and wait +``` + +```scratch:split:random +set [total 1 v] to [0] + +set [total 2 v] to [0] + +set [counter v] to [1] + +change [counter v] by (1) + +change [total 1 v] by (answer) + +change [counter v] by (1) + +change [total 2 v] by (answer) + +set [total v] to (((total 1) * (3)) + (total 2)) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-at-a-time/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-at-a-time/scratch-hints.md new file mode 100644 index 000000000..5a55696c3 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-at-a-time/scratch-hints.md @@ -0,0 +1,5 @@ +- You need to add up 1st, 3rd, 5th and so on digits and store the result + in a variable called total 1. + Then add up 2nd, 4th, 6th and so on digits and store the result in + variable called total 2. + The output is the sum of total 1 multiplied by 3 and total 2. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-at-a-time/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-at-a-time/scratch-solution.md new file mode 100644 index 000000000..9d4e209b3 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-at-a-time/scratch-solution.md @@ -0,0 +1,16 @@ +```scratch +when green flag clicked +set [total 1 v] to [0] +set [total 2 v] to [0] +set [counter v] to [1] +repeat (6) + ask (join (join [Enter digit ] (counter)) [ of the product code:]) and wait + change [counter v] by (1) + change [total 1 v] by (answer) + ask (join (join [Enter digit ] (counter)) [ of the product code:]) and wait + change [counter v] by (1) + change [total 2 v] by (answer) +end +set [total v] to (((total 1) * (3)) + (total 2)) +say (join [Total: ] (total)) +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-line/product-code-12-multiple-10-one-line.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-line/product-code-12-multiple-10-one-line.md new file mode 100644 index 000000000..78144be02 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-line/product-code-12-multiple-10-one-line.md @@ -0,0 +1,23 @@ +# 12 digit product codes: Check if total is a multiple of 10 (one line of input) + +## Requirement: + +Write a program that asks the user to enter a 12 digit product code, all in +one line of input. +Then adds up all the digits, every second one multiplied by 3 (1st, 3rd, +and so on) and show if this total is a multiple of 10 (valid product code). +For this challenge make sure the user can only enter a 12 digit product code. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +| ------------ | --------------------- | +| 838310022524 | Valid product code! | +| 811571013579 | Valid product code! | +| 838310032524 | Invalid product code! | +| 614141000012 | Valid product code! | +| 811571023579 | Invalid product code! | +| 664141000012 | Invalid product code! | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-line/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-line/scratch-expected.md new file mode 100644 index 000000000..918bb1a9d --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-line/scratch-expected.md @@ -0,0 +1,60 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148476927/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Enter a 12 digit product code:] and wait +``` + + +```scratch:split:random +set [index v] to [1] + +set [product code v] to [] + +set [total 1 v] to [0] + +set [total 2 v] to [0] + +set [total v] to [0] + +set [total 1 v] to ((total 1) + (letter (index) of (product code))) + +change [index v] by (1) + +set [total 2 v] to ((total 2) + (letter (index) of (product code))) + +change [index v] by (1) + +set [total v] to (((total 1) * (3)) + (total 2)) + +change [product code v] by (answer) +``` + +```scratch:split:random +say [Valid product code!] + +say [Invalid product code!] + +say [Please enter a 12 digit number!] +``` + +```scratch:split:random +if <(length of (product code)) = [12]> then +else +end + +repeat (6) +end + +if <((total) mod (10)) = [0]> then +else +end +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-line/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-line/scratch-hints.md new file mode 100644 index 000000000..722a40235 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-line/scratch-hints.md @@ -0,0 +1,14 @@ +- You can find the number digits in a number by using the + `scratch:length of ()` block unders “Operators”. + For example: `scratch:length of (293833992383) //12` + +- You can access a letter (or a digit) at a specified position in a string + (or number) by using the `scratch:letter () of []` block under “Operators”. + For example: `scratch:letter (4) of [18392819202910] //9` + +- In this challenge you need to access all the digits of the product code + number entered, adding the 1st, 3rd, 5th and so on digits and storing + the result in variable "total 1" and adding the 2nd, 4th, 6th and so + on digits and storing the result in variable "total 2". + The output displays if the sum of "total 1" multiplied by 3 and "total 2" + is a multiple of 10 or not. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-line/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-line/scratch-solution.md new file mode 100644 index 000000000..268c9a29a --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-multiple-10-one-line/scratch-solution.md @@ -0,0 +1,26 @@ +```scratch +when green flag clicked +set [index v] to [1] +set [product code v] to [] +set [total 1 v] to [0] +set [total 2 v] to [0] +set [total v] to [0] +ask [Enter a 12 digit product code:] and wait +change [product code v] by (answer) +if <(length of (product code)) = [12]> then + repeat (6) + set [total 1 v] to ((total 1) + (letter (index) of (product code))) + change [index v] by (1) + set [total 2 v] to ((total 2) + (letter (index) of (product code))) + change [index v] by (1) + end + set [total v] to (((total 1) * (3)) + (total 2)) + if <((total) mod (10)) = [0]> then + say [Valid product code!] + else + say [Invalid product code!] + end +else + say [Please enter a 12 digit number!] +end +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-weighted-sum-one-at-a-time/product-code-12-weighted-sum-one-at-a-time.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-weighted-sum-one-at-a-time/product-code-12-weighted-sum-one-at-a-time.md new file mode 100644 index 000000000..9b655d317 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-weighted-sum-one-at-a-time/product-code-12-weighted-sum-one-at-a-time.md @@ -0,0 +1,17 @@ +# 12 digit product codes: Weighted sum of digits (entered one at a time) + +## Requirement: + +Write a program that asks the user to enter a 12 digit product code, +entering each digit one at a time. +Then adds up all the digits, every second one multiplied by 3 (1st, 3rd, +and so on), and shows the total as the output. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +| -------------------------------------------------------- | --------- | +| 8
3
8
3
1
0
0
2
2
5
2
4 | Total: 80 | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-weighted-sum-one-at-a-time/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-weighted-sum-one-at-a-time/scratch-expected.md new file mode 100644 index 000000000..18736e1d0 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-weighted-sum-one-at-a-time/scratch-expected.md @@ -0,0 +1,51 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148476857/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked +``` + +```scratch:split:random +ask (join (join [Enter digit ] (counter)) [ of the product code:]) and wait + +ask (join (join [Enter digit ] (counter)) [ of the product code:]) and wait +``` + +```scratch:split:random +repeat (6) +end + +if <((total) mod (10)) = [0]> then +else +end +``` + +```scratch:split:random +say [The sum is a multiple of 10] + +say [The sum is NOT a multiple of 10] +``` + +```scratch:split:random +set [total 1 v] to [0] + +set [total 2 v] to [0] + +set [counter v] to [1] + +change [counter v] by (1) + +change [total 1 v] by (answer) + +change [counter v] by (1) + +change [total 2 v] by (answer) + +set [total v] to (((total 1) * (3)) + (total 2)) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-weighted-sum-one-at-a-time/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-weighted-sum-one-at-a-time/scratch-hints.md new file mode 100644 index 000000000..6b8c3dc51 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-weighted-sum-one-at-a-time/scratch-hints.md @@ -0,0 +1,8 @@ +- You need to add up 1st, 3rd, 5th and so on digits and store the + result in a variable called total 1. + Then add up 2nd, 4th, 6th and so on digits and store the result in + variable called total 2. + Store the sum of total 1 multiplied by 3 and total 2 in a variable + called total. + The output displays if this total is a multiple of 10 or not + (i.e. if “total” mod 10 is equal to 0). diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-weighted-sum-one-at-a-time/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-weighted-sum-one-at-a-time/scratch-solution.md new file mode 100644 index 000000000..793594faf --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-12-weighted-sum-one-at-a-time/scratch-solution.md @@ -0,0 +1,20 @@ +```scratch +when green flag clicked +set [total 1 v] to [0] +set [total 2 v] to [0] +set [counter v] to [1] +repeat (6) + ask (join (join [Enter digit ] (counter)) [ of the product code:]) and wait + change [counter v] by (1) + change [total 1 v] by (answer) + ask (join (join [Enter digit ] (counter)) [ of the product code:]) and wait + change [counter v] by (1) + change [total 2 v] by (answer) +end +set [total v] to (((total 1) * (3)) + (total 2)) +if <((total) mod (10)) = [0]> then + say [The sum is a multiple of 10] +else + say [The sum is NOT a multiple of 10] +end +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-at-a-time/product-code-13-last-digit-one-at-a-time.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-at-a-time/product-code-13-last-digit-one-at-a-time.md new file mode 100644 index 000000000..bf01ce7fa --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-at-a-time/product-code-13-last-digit-one-at-a-time.md @@ -0,0 +1,17 @@ +# 13 digit product codes: Calculate the last digit (entered one digit at a time) + +## Requirement: + +Write a program that asks the user to enter the first 12 digits of a 13 +digit product code, one at a time, and works out the last digit as the output. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +| -------------------------------------------------------- | -------------------- | +| 9
3
2
8
3
5
0
0
0
3
7
1 | The last digit is: 9 | +| 9
3
0
0
7
2
8
8
5
4
5
4 | The last digit is: 3 | +| 9
4
1
4
2
2
6
1
0
0
8
4 | The last digit is: 9 | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-at-a-time/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-at-a-time/scratch-expected.md new file mode 100644 index 000000000..53fa2d336 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-at-a-time/scratch-expected.md @@ -0,0 +1,43 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148476646/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +say (join [The last digit of the product code is ] (last digit)) + +repeat (6) +end +``` + +```scratch:split:random +ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait + +ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait +``` + +```scratch:split:random +set [odd total v] to [0] + +set [even total v] to [0] + +set [index v] to [1] + +set [last digit v] to [0] + +change [index v] by (1) + +change [odd total v] by (answer) + +change [index v] by (1) + +change [even total v] by (answer) + +set [last digit v] to (((0) - ((odd total) + ((even total) * (3)))) mod (10)) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-at-a-time/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-at-a-time/scratch-hints.md new file mode 100644 index 000000000..b20c089a6 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-at-a-time/scratch-hints.md @@ -0,0 +1,6 @@ +- You need to add up 1st, 3rd, 5th and so on digits and store the result in + a variable called "total 1". + Then add up 2nd, 4th, 6th and so on digits and store the result in + variable called "total 2". + The last digit (check digit) is then calculated by subtracting the sum + of "total 2" multiplied by 3, and "total 1" from 0 mod 10. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-at-a-time/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-at-a-time/scratch-solution.md new file mode 100644 index 000000000..8716d9e91 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-at-a-time/scratch-solution.md @@ -0,0 +1,17 @@ +```scratch +when green flag clicked +set [odd total v] to [0] +set [even total v] to [0] +set [index v] to [1] +set [last digit v] to [0] +repeat (6) + ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait + change [index v] by (1) + change [odd total v] by (answer) + ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait + change [index v] by (1) + change [even total v] by (answer) +end +set [last digit v] to (((0) - ((odd total) + ((even total) * (3)))) mod (10)) +say (join [The last digit of the product code is ] (last digit)) +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-line/product-code-13-last-digit-one-line.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-line/product-code-13-last-digit-one-line.md new file mode 100644 index 000000000..06e88bc6a --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-line/product-code-13-last-digit-one-line.md @@ -0,0 +1,20 @@ +# 13 digit product codes: Calculate the last digit (one line of input) + +## Requirement: + +Write a program that asks the user to enter the first 12 digits of a +product code, in one line of input, and works out the last digit as the output. +For this challenge make sure the user can only enter a 13 digit product +code (the first 12 digits). + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +| ------------ | -------------------- | +| 941887000085 | The last digit is: 2 | +| 941527700012 | The last digit is: 6 | +| 930072885454 | The last digit is: 3 | +| 932835000371 | The last digit is: 9 | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-line/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-line/scratch-expected.md new file mode 100644 index 000000000..1c36415d2 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-line/scratch-expected.md @@ -0,0 +1,53 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148476672/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Enter first 12 digits of the barcode:] and wait +``` + +```scratch:split:random +repeat (6) +end + +if <(length of (first 12 digits)) = [12]> then +else +end +``` + +```scratch:split:random +say [You must enter a 12 digit number!] + +say (join [The last digit of the product code is: ] (last digit)) +``` + +```scratch:split:random +set [index v] to [1] + +set [first 12 digits v] to [0] + +set [total 1 v] to [0] + +set [total 2 v] to [0] + +set [last digit v] to [0] + +set [last digit v] to (((0) - ((total 1) + ((total 2) * (3)))) mod (10)) + +set [first 12 digits v] to (answer) + +set [total 1 v] to ((total 1) + (letter (index) of (first 12 digits))) + +change [index v] by (1) + +set [total 2 v] to ((total 2) + (letter (index) of (first 12 digits))) + +change [index v] by (1) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-line/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-line/scratch-hints.md new file mode 100644 index 000000000..e1f9be9cc --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-line/scratch-hints.md @@ -0,0 +1,10 @@ +- For the 12 digits entered, you need to add up 1st, 3rd, 5th and so on + digits and store the result in a variable called "total 1". + Then add up 2nd, 4th, 6th and so on digits and store the result in + variable called "total 2". + The last digit (check digit) is then calculated by subtracting the sum + of "total 2" multiplied by 3 and "total 1" from 0 mod 10. + +- You can access a letter (or a digit) at a specified position in a string + (or number) by using the `scratch:letter () of []` block under “Operators”. + For example: `scratch:letter (4) of [18392819202910] //9` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-line/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-line/scratch-solution.md new file mode 100644 index 000000000..de9c5be3c --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-last-digit-one-line/scratch-solution.md @@ -0,0 +1,22 @@ +```scratch +when green flag clicked +set [index v] to [1] +set [first 12 digits v] to [0] +set [total 1 v] to [0] +set [total 2 v] to [0] +set [last digit v] to [0] +ask [Enter first 12 digits of the barcode:] and wait +set [first 12 digits v] to (answer) +if <(length of (first 12 digits)) = [12]> then + repeat (6) + set [total 1 v] to ((total 1) + (letter (index) of (first 12 digits))) + change [index v] by (1) + set [total 2 v] to ((total 2) + (letter (index) of (first 12 digits))) + change [index v] by (1) + end + set [last digit v] to (((0) - ((total 1) + ((total 2) * (3)))) mod (10)) + say (join [The last digit of the product code is: ] (last digit)) +else + say [You must enter a 12 digit number!] +end +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-at-a-time/product-code-13-multiple-10-one-at-a-time.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-at-a-time/product-code-13-multiple-10-one-at-a-time.md new file mode 100644 index 000000000..41395168c --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-at-a-time/product-code-13-multiple-10-one-at-a-time.md @@ -0,0 +1,65 @@ +# 13 digit product codes: Check if total is a multiple of 10 (entered one at a time) + +## Requirement: + +Write a program that asks the user to enter a 13 digit product code, +entering each digit one at a time. +Then adds up all the digits, every second one multiplied by 3 (2nd, 4th, +and so on) and show if this total is a multiple of 10 or not. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +{panel type="general" title="Example"} + +**Input** +``` +Enter digit 1 of the product code: +9 + +Enter digit 2 of the product code: +4 + +Enter digit 3 of the product code: +2 + +Enter digit 4 of the product code: +1 + +Enter digit 5 of the product code: +9 + +Enter digit 6 of the product code: +0 + +Enter digit 7 of the product code: +2 + +Enter digit 8 of the product code: +8 + +Enter digit 9 of the product code: +1 + +Enter digit 10 of the product code: +4 + +Enter digit 11 of the product code: +7 + +Enter digit 12 of the product code: +9 + +Enter digit 13 of the product code: +2 +``` + +**Output** + +``` +The sum is a multiple of 10 +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-at-a-time/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-at-a-time/scratch-expected.md new file mode 100644 index 000000000..52ef0291c --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-at-a-time/scratch-expected.md @@ -0,0 +1,55 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148476573/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked +``` + +```scratch:split:random +say [The sum is NOT a multiple of 10] + +say [The sum is a multiple of 10] +``` + +```scratch:split:random +ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait + +ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait + +ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait +``` + +```scratch:split:random +repeat (6) +end + +if <((total) mod (10)) = [0]> then +else +end +``` + +```scratch:split:random +set [odd total v] to [0] + +set [even total v] to [0] + +set [index v] to [1] + +change [odd total v] by (answer) + +set [total v] to ((odd total) + ((even total) * (3))) + +change [index v] by (1) + +change [odd total v] by (answer) + +change [index v] by (1) + +change [even total v] by (answer) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-at-a-time/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-at-a-time/scratch-hints.md new file mode 100644 index 000000000..54df8cd1b --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-at-a-time/scratch-hints.md @@ -0,0 +1,8 @@ +- You need to add up 1st, 3rd, 5th and so on digits and store the result + in a variable called "total 1". + Then add up 2nd, 4th, 6th and so on digits and store the result in a + variable called "total 2". + Store the sum of "total 2" multiplied by 3 and "total 1" in a variable + called "total". + The output displays if this total is a multiple of 10 or not + (i.e. if “total” mod 10 is equal to 0). diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-at-a-time/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-at-a-time/scratch-solution.md new file mode 100644 index 000000000..ba83144b6 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-at-a-time/scratch-solution.md @@ -0,0 +1,22 @@ +```scratch +when green flag clicked +set [odd total v] to [0] +set [even total v] to [0] +set [index v] to [1] +repeat (6) + ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait + change [index v] by (1) + change [odd total v] by (answer) + ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait + change [index v] by (1) + change [even total v] by (answer) +end +ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait +change [odd total v] by (answer) +set [total v] to ((odd total) + ((even total) * (3))) +if <((total) mod (10)) = [0]> then + say [The sum is a multiple of 10] +else + say [The sum is NOT a multiple of 10] +end +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-line/product-code-13-multiple-10-one-line.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-line/product-code-13-multiple-10-one-line.md new file mode 100644 index 000000000..e300d3546 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-line/product-code-13-multiple-10-one-line.md @@ -0,0 +1,23 @@ +# 13 digit product codes: Check if total is a multiple of 10 (one line of input) + +## Requirement: + +Write a program that asks the user to enter a 13 digit product code, all +in one line of input. +Then adds up all the digits, every second one multiplied by 3 (2nd, 4th, and +so on) and show if this total is a multiple of 10 (valid product code). +For this challenge make sure the user can only enter a 13 digit product code. + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +| ------------- | --------------------- | +| 9418870000852 | Valid product code! | +| 9415277000126 | Valid product code! | +| 9300728854542 | Invalid product code! | +| 9328350003719 | Valid product code! | +| 9415377000126 | Invalid product code! | +| 9418770000852 | Invalid product code! | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-line/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-line/scratch-expected.md new file mode 100644 index 000000000..d44e98658 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-line/scratch-expected.md @@ -0,0 +1,62 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148476604/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Enter a 13-digit product code:] and wait +``` + +```scratch:split:random +say [Please enter a 13 digit number!] + +say [Invalid product code!] + +say [Valid product code!] +``` + +```scratch:split:random +repeat (6) +end + +if <((total) mod (10)) = [0]> then +else +end + +if <(length of (product code)) = [13]> then +else + +end +``` + +```scratch:split:random +set [index v] to [1] + +set [product code v] to [] + +set [total 1 v] to [0] + +set [total 2 v] to [0] + +set [total v] to [0] + +set [total 1 v] to ((total 1) + (letter (index) of (product code))) + +set [total v] to ((total 1) + ((total 2) * (3))) + +set [total 1 v] to ((total 1) + (letter (index) of (product code))) + +change [index v] by (1) + +set [total 2 v] to ((total 2) + (letter (index) of (product code))) + +change [index v] by (1) + +change [product code v] by (answer) +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-line/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-line/scratch-hints.md new file mode 100644 index 000000000..85196ec5f --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-line/scratch-hints.md @@ -0,0 +1,14 @@ +- You can find the number digits in a number by using the + `scratch:length of ()` block unders “Operators”. + For example: `scratch:length of (2938339392383) //13` + +- You can access a letter (or a digit) at a specified position in a string + (or number) by using the `scratch:letter () of []` block under “Operators”. + For example: `scratch:letter (4) of [18392819202910] //9` + +- In this challenge you need to access all the digits of the product code + number entered, adding the 1st, 3rd, 5th and so on digits and storing + the result in variable "total 1" and adding the 2nd, 4th, 6th and so + on digits and storing the result in variable "total 2". + The output displays if the sum of "total 2" multiplied by 3 and "total 1" + is a multiple of 10 or not. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-line/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-line/scratch-solution.md new file mode 100644 index 000000000..c5864026e --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-multiple-10-one-line/scratch-solution.md @@ -0,0 +1,27 @@ +```scratch +when green flag clicked +set [index v] to [1] +set [product code v] to [] +set [total 1 v] to [0] +set [total 2 v] to [0] +set [total v] to [0] +ask [Enter a 13-digit product code:] and wait +change [product code v] by (answer) +if <(length of (product code)) = [13]> then + repeat (6) + set [total 1 v] to ((total 1) + (letter (index) of (product code))) + change [index v] by (1) + set [total 2 v] to ((total 2) + (letter (index) of (product code))) + change [index v] by (1) + end + set [total 1 v] to ((total 1) + (letter (index) of (product code))) + set [total v] to ((total 1) + ((total 2) * (3))) + if <((total) mod (10)) = [0]> then + say [Valid product code!] + else + say [Invalid product code!] + end +else + say [Please enter a 13 digit number!] +end +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-weighted-sum-one-at-a-time/product-code-13-weighted-sum-one-at-a-time.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-weighted-sum-one-at-a-time/product-code-13-weighted-sum-one-at-a-time.md new file mode 100644 index 000000000..e01a1558c --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-weighted-sum-one-at-a-time/product-code-13-weighted-sum-one-at-a-time.md @@ -0,0 +1,17 @@ +# 13 digit product codes: Weighted sum of digits (entered one at a time) + +## Requirement: + +Write a program that asks the user to enter a 13 digit product code, +entering each digit one at a time. +Then adds up all the digits, every second one multiplied by 3 (2nd, 4th, and +so on), and shows the total as the output. + +## Testing examples: + +Your program should display the outputs shown in this panel for the given +inputs provided: + +| Input | Output | +| ------------------------------------------------------------- | ---------- | +| 9
4
2
1
9
0
2
8
1
4
7
9
2 | Total: 110 | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-weighted-sum-one-at-a-time/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-weighted-sum-one-at-a-time/scratch-expected.md new file mode 100644 index 000000000..d68e9b651 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-weighted-sum-one-at-a-time/scratch-expected.md @@ -0,0 +1,47 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148476545/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +say (join [Total: ] (total)) +``` + +```scratch:split:random +repeat (6) +end +``` + +```scratch:split:random +ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait + +ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait + +ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait +``` + +```scratch:split:random +change [total 1 v] by (answer) + +set [total v] to ((total 1) + ((total 2) * (3))) + +change [index v] by (1) + +change [total 1 v] by (answer) + +change [index v] by (1) + +change [total 2 v] by (answer) + +set [total 1 v] to [0] + +set [total 2 v] to [0] + +set [index v] to [1] +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-weighted-sum-one-at-a-time/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-weighted-sum-one-at-a-time/scratch-hints.md new file mode 100644 index 000000000..116741adc --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-weighted-sum-one-at-a-time/scratch-hints.md @@ -0,0 +1,5 @@ +- You need to add up 1st, 3rd, 5th and so on digits and store the result + in a variable called total 1. + Then add up 2nd, 4th, 6th and so on digits and store the result in + variable called total 2. + The output is the sum of total 2 multiplied by 3 and total 1. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-weighted-sum-one-at-a-time/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-weighted-sum-one-at-a-time/scratch-solution.md new file mode 100644 index 000000000..a6f413022 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-13-weighted-sum-one-at-a-time/scratch-solution.md @@ -0,0 +1,18 @@ +```scratch +when green flag clicked +set [total 1 v] to [0] +set [total 2 v] to [0] +set [index v] to [1] +repeat (6) + ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait + change [index v] by (1) + change [total 1 v] by (answer) + ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait + change [index v] by (1) + change [total 2 v] by (answer) +end +ask (join (join [Enter digit ] (index)) [ of the product code:]) and wait +change [total 1 v] by (answer) +set [total v] to ((total 1) + ((total 2) * (3))) +say (join [Total: ] (total)) +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-check-valid-any-length/product-code-check-valid-any-length.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-check-valid-any-length/product-code-check-valid-any-length.md new file mode 100644 index 000000000..0a19afaa8 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-check-valid-any-length/product-code-check-valid-any-length.md @@ -0,0 +1,41 @@ +# 12 and 13 digit product codes: Check for a valid product code (any length) + +## Requirement: + +Write a program that asks the user to enter a product code of any length in +one line of input and adds up all the digits (every second one multiplied +by 3). +To add up the digits you need to start from the last digit, multiply the last +digit by 1, second to last by 3 and so on. +It then checks if the product code is valid (total number is a multiple of 10). +Starting at the right-hand digit will make the formula work for a wide +variety of product codes. + +(Note that this rule will work for most of the common product codes on shop +products, which use the alternating multipliers of 3 and 1. +The check digit is always multiplied by 1, so starting at the right is an +easy way to get the sequence correct. +Common codes that you may come across are GTIN-13, GTIN-12, GTIN-8, EAN-13, +EAN-8 and UPC-A, and these all use the alternating 1/3 formula. +An exception is UPC-E, which is very complex. +UPC-E is most common in America; it as an 8-digit code used on smaller items +such as soda cans. +Because the structure of UPC-E is so complex, it's best to not use them +unless you have advanced students wanting a difficult challenge. +UPC-E product codes can be recognised because they do not have a double line +from the bars extending down between the two sets of four digits i.e. +the 8 digits are all written without any gaps.) + +## Testing examples: + +Your program should display the outputs shown in this table for the given +inputs provided: + +| Input | Output | +| ------------- | --------------------- | +| 50184385 | Valid product code! | +| 036000241457 | Valid product code! | +| 50174385 | Invalid product code! | +| 9330462119318 | Valid product code! | +| 9330463119318 | Invalid product code! | +| 036002241457 | Invalid product code! | diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-check-valid-any-length/scratch-expected.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-check-valid-any-length/scratch-expected.md new file mode 100644 index 000000000..e6ae04666 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-check-valid-any-length/scratch-expected.md @@ -0,0 +1,53 @@ +Click on the green flag, enter the inputs provided in the “testing examples” to +see the expected output of your program. + +{iframe link="https://scratch.mit.edu/projects/embed/148476969/?autostart=false"} + +{panel type="help" title="Recommended blocks"} + +```scratch:split:random +when green flag clicked + +ask [Enter a product code:] and wait +``` + +```scratch:split:random +set [total v] to ((total 1) + ((total 2) * (3))) + +set [product code v] to [0] + +set [total 1 v] to [0] + +set [total 2 v] to [0] + +set [total v] to [0] + +change [product code v] by (answer) + +set [index v] to (length of (product code)) + +set [total 1 v] to ((total 1) + (letter (index) of (product code))) + +change [index v] by (-1) + +set [total 2 v] to ((total 2) + (letter (index) of (product code))) + +change [index v] by (-1) +``` + +```scratch:split:random +repeat until <(index) < [1]> +end + +if <((total) mod (10)) = [0]> then +else +end +``` + +```scratch:split:random +say [This is a valid product code] + +say [This is an invalid product code] +``` + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-check-valid-any-length/scratch-hints.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-check-valid-any-length/scratch-hints.md new file mode 100644 index 000000000..c577d2c08 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-check-valid-any-length/scratch-hints.md @@ -0,0 +1,18 @@ +- To add up the digits you need to start from the last digit, multiply the + last digit by 1, second to last by 3 and so on. + It then checks if the product code is valid (total number is a multiple + of 10). + Starting at the right-hand digit will make the formula work for a wide variety of product codes. + +- You can find the number digits in a number by using the + `scratch:length of ()` block unders “Operators”. + For example: `scratch:length of (2938339392383) //13` + +- You can access a letter (or a digit) at a specified position in a string + (or number) by using the `scratch:letter () of []` block under “Operators”. + For example: `scratch:letter (4) of [18392819202910] //9` + +- To access the last digit of the product code, make a variable called + “index” and set it to the length of the product code. + Now the last digit of the product code can be accessed by using the + `scratch:length of ()` block with the index variable and the product code. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-check-valid-any-length/scratch-solution.md b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-check-valid-any-length/scratch-solution.md new file mode 100644 index 000000000..411d4a0a7 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/product-code-check-valid-any-length/scratch-solution.md @@ -0,0 +1,22 @@ +```scratch +when green flag clicked +set [product code v] to [0] +set [total 1 v] to [0] +set [total 2 v] to [0] +set [total v] to [0] +ask [Enter a product code:] and wait +change [product code v] by (answer) +set [index v] to (length of (product code)) +repeat until <(index) < [1]> + set [total 1 v] to ((total 1) + (letter (index) of (product code))) + change [index v] by (-1) + set [total 2 v] to ((total 2) + (letter (index) of (product code))) + change [index v] by (-1) +end +set [total v] to ((total 1) + ((total 2) * (3))) +if <((total) mod (10)) = [0]> then + say [This is a valid product code] +else + say [This is an invalid product code] +end +``` diff --git a/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/programming-challenges.yaml b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/programming-challenges.yaml new file mode 100644 index 000000000..77a8d5c95 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/programming-challenges/programming-challenges.yaml @@ -0,0 +1,276 @@ +count-black-squares-one-colour-input: + challenge-set-number: 1 + challenge-number: 1 + difficulty-level: 1 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-identify-if-statement + - programming-explain-set-and-change-statement + +count-black-squares-one-line-input: + challenge-set-number: 1 + challenge-number: 2 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-set-and-change-statement + - programming-describe-variable-initial-value + - programming-identify-if-statement + +check-odd-even-using-mod: + challenge-set-number: 2 + challenge-number: 1 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-identify-if-statement + +check-odd-even-using-subtraction: + challenge-set-number: 2 + challenge-number: 2 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-describe-variables + - programming-identify-if-statement + +detect-parity-error-in-row: + challenge-set-number: 3 + challenge-number: 1 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-variable-name + - programming-explain-mod + - programming-explain-differences-if-else-if-statements + +detect-parity-error-in-six-rows: + challenge-set-number: 4 + challenge-number: 1 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-variable-name + - programming-explain-join-concatenate + - programming-explain-mod + - programming-explain-differences-if-else-if-statements + +detect-parity-error-in-any-rows: + challenge-set-number: 4 + challenge-number: 2 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-join-concatenate + - programming-describe-variables + - programming-explain-mod + - programming-explain-differences-if-else-if-statements + +detect-parity-error-in-any-rows-after-input: + challenge-set-number: 5 + challenge-number: 1 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-describe-variables + - programming-identify-if-statement + - programming-explain-join-concatenate + - programming-identify-list-store-data + +detect-parity-error-in-any-rows-or-columns: + challenge-set-number: 6 + challenge-number: 1 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-join-concatenate + - programming-identify-if-statement + - programming-describe-variables + - programming-identify-list-store-data + extra-challenge: extra-challenge.md + +product-code-12-weighted-sum-one-at-a-time: + challenge-set-number: 10 + challenge-number: 1 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-explain-join-concatenate + +product-code-13-weighted-sum-one-at-a-time: + challenge-set-number: 10 + challenge-number: 1 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-asking-input-end-user + - programming-describe-variables + - programming-explain-join-concatenate + +product-code-12-multiple-10-one-at-a-time: + challenge-set-number: 10 + challenge-number: 2 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-describe-variables + - programming-identify-if-statement + - programming-explain-join-concatenate + - programming-explain-asking-input-end-user + - programming-explain-mod + +product-code-13-multiple-10-one-at-a-time: + challenge-set-number: 10 + challenge-number: 2 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-describe-variables + - programming-identify-if-statement + - programming-explain-join-concatenate + - programming-explain-asking-input-end-user + - programming-explain-mod + +product-code-12-multiple-10-one-line: + challenge-set-number: 10 + challenge-number: 3 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-variable-name + - programming-explain-set-and-change-statement + - programming-describe-nested-statement + +product-code-13-multiple-10-one-line: + challenge-set-number: 10 + challenge-number: 3 + difficulty-level: 2 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-variable-name + - programming-explain-set-and-change-statement + - programming-describe-nested-statement + +product-code-check-valid-any-length: + challenge-set-number: 10 + challenge-number: 4 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-variable-name + - programming-explain-set-and-change-statement + - programming-describe-nested-statement + +product-code-12-last-digit-one-at-a-time: + challenge-set-number: 10 + challenge-number: 5 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-join-concatenate + - programming-explain-set-and-change-statement + - programming-demonstrate-indexing + - programming-identify-loop + +product-code-13-last-digit-one-at-a-time: + challenge-set-number: 10 + challenge-number: 5 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-join-concatenate + - programming-explain-set-and-change-statement + - programming-demonstrate-indexing + - programming-identify-loop + +product-code-12-last-digit-one-line: + challenge-set-number: 10 + challenge-number: 6 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-join-concatenate + - programming-explain-set-and-change-statement + - programming-demonstrate-indexing + +product-code-13-last-digit-one-line: + challenge-set-number: 10 + challenge-number: 6 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-explain-join-concatenate + - programming-explain-set-and-change-statement + - programming-demonstrate-indexing + +check-valid-credit-card: + challenge-set-number: 20 + challenge-number: 1 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-describe-variables + - programming-outline-variable-updates + - programming-identify-if-statement + - programming-explain-variable-name + +calculate-credit-card-last-digit: + challenge-set-number: 20 + challenge-number: 2 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-describe-variables + - programming-outline-variable-updates + - programming-identify-if-statement + - programming-explain-variable-name + +check-valid-isbn-10: + challenge-set-number: 21 + challenge-number: 1 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-describe-variables + - programming-outline-variable-updates + - programming-identify-if-statement + - programming-explain-variable-name + +calculate-isbn-10-last-digit: + challenge-set-number: 21 + challenge-number: 2 + difficulty-level: 3 + programming-languages: + - scratch + learning-outcomes: + - programming-describe-variables + - programming-outline-variable-updates + - programming-identify-if-statement + - programming-explain-variable-name diff --git a/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/lessons.yaml b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/lessons.yaml new file mode 100644 index 000000000..7fda5ce4b --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/lessons.yaml @@ -0,0 +1,48 @@ +parity-magic: + duration: 30 + computational-thinking-links: parity-magic-ct-links.md + learning-outcomes: + - numeracy-explain-even-odd-parity-problem + - numeracy-describe-rows-columns + - numeracy-discuss-changing-one-card-state + - error-describe-steps-to-find-card + - error-explain-each-card-bit + - error-explain-chose-parity-card + generated-resources: + parity-cards: + description: Two pages of parity cards per pair of students. + classroom-resources: + - A set of 36 parity cards. + - A metal board (ideally a whiteboard) if magnetic parity cards are being + used. + programming-challenges: + - count-black-squares-one-colour-input + - count-black-squares-one-line-input + - check-odd-even-using-mod + - check-odd-even-using-subtraction + - detect-parity-error-in-row + - detect-parity-error-in-six-rows + - detect-parity-error-in-any-rows + - detect-parity-error-in-any-rows-after-input + - detect-parity-error-in-any-rows-or-columns + +product-code-check-digits: + duration: 30 + computational-thinking-links: product-code-check-digits-ct-links.md + classroom-resources: + - A number of items with a 12 or 13 digit barcode on it (technique is + almost identical for both). + - A whiteboard and/or paper to write the calculations on. + programming-challenges: + - product-code-12-weighted-sum-one-at-a-time + - product-code-13-weighted-sum-one-at-a-time + - product-code-12-multiple-10-one-at-a-time + - product-code-13-multiple-10-one-at-a-time + - product-code-12-multiple-10-one-line + - product-code-13-multiple-10-one-line + - product-code-check-valid-any-length + - product-code-12-last-digit-one-at-a-time + - product-code-13-last-digit-one-at-a-time + - product-code-12-last-digit-one-line + - product-code-13-last-digit-one-line + programming-challenges-description: product-code-check-digits-programming.md diff --git a/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/parity-magic-ct-links.md b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/parity-magic-ct-links.md new file mode 100644 index 000000000..b861f4c7b --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/parity-magic-ct-links.md @@ -0,0 +1,119 @@ +{panel type="ct-algorithm" title="Algorithmic thinking"} + +The step by step process of counting the number of black squares in each row +and identifying whether the parity is even, and then repeating the process +with each column, is an example of an algorithm. +The full error detection algorithm includes what to to do when the error is +found as well. + +#### Examples of what you could look for: + +Can students reliably find the flipped card in a grid? +Can they articulate the algorithm to another student to help them complete the +task? + +{panel end} + +{panel type="ct-abstraction" title="Abstraction"} + +Each of the cards in this activity represents a bit inside a digital device. +When we arrange all of our ‘bits’ together in the square this is a +representation of some data (admittedly random data in the magic trick, but it +could be done with real data!). +This means our grid is an abstraction because it is a model that could be used +to represent any piece of data that can be stored on a digital device, for +example a sound file, a video, or even a piece of code! + +#### Examples of what you could look for: + +Are students able to explain that each card is a bit and that the cards can +represent data? +Can they see the connection between the grid of cards and a piece of data made +up of bytes? + +{panel end} + +{panel type="ct-decomposition" title="Decomposition"} + +To accomplish this task students must decompose the process “find the error” +into smaller steps. +The first step students break it down to could be “look at each row, one by +one, until I find an error”. +Students can break it down further, focusing on one row, and asking themselves +whether the row has an even or odd number of black cards. +They are decomposing the bigger problem down to a subtask, which they can then +work through and move closer to completing the full problem. +Likewise when they need to find the column with an error they can break this +down in the same way, focusing on one column, and asking the same question +above: does the column have an even or odd number of black cards? +These processes are much simpler to solve that the big problem “Find the +error”, but if you work through them all then you will have completely solved +that big problem! + +#### Examples of what you could look for: + +Are students able to break the task down into steps and describe these steps, +without having to have the algorithm described to them first? + +{panel end} + +{panel type="ct-pattern" title="Generalising and patterns"} + +Once students understand the algorithm to find the card that has been turned, +they will be able to find a (single) flipped card for any size grid; a 10 x 10 +grid (100 cards) can be done fairly quickly, and even a 20 x 20 grid is +possible given enough time (and cards!). +They can generalise the problem “find the error in a **6 x 6 grid**” to: +“find the error in **a grid**”. + +#### Examples of what you could look for: + +Can students find the flipped card on larger grids? + +{panel end} + +{panel type="ct-evaluation" title="Evaluation"} + +The parity trick is always able to detect and correct errors if one bit has +been flipped, but it is important to evaluate this algorithm for different +kinds of errors as well. +If more than one card is flipped (i.e. more than one error occurs) then we can +tell something is wrong with the data, but our algorithm can’t tell us how to +fix this! +Even worse, if more than two bits are flipped then sometimes we might not even +be able to detect the error! +It’s important that we evaluate this algorithm, because if it is going to fail +sometimes we need to know. +Computer Scientists evaluate algorithms like these and improve them when they +find problems with them. + +#### Examples of what you could look for: + +Ask students “What kind of errors can be detected and corrected with the +parity cards? +At what point can you only detect but not correct and error? +Why is that?” + +Can students explain what goes wrong when we try to detect the error if two +cards are flipped? + +{panel end} + +{panel type="ct-logic" title="Logic"} + +Flipping a card will always change a row/column from odd to even, no matter +what the card is in what the other cards in the row/column are. + +Also, the idea that the corner card is correct for both the new row and column +is an advanced concept, but a pattern that some students might recognise. + +#### Examples of what you could look for: + +Can students explain why a single card flip always changes the number of black +cards to an odd number? + +Can students explain why the corner card will be correct for both the row and +column? +(This involves fairly advanced reasoning). + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/parity-magic.md b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/parity-magic.md new file mode 100644 index 000000000..2ebab638a --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/parity-magic.md @@ -0,0 +1,289 @@ +# Parity magic + +{panel type="teaching" title="See teaching this in action!"} + +A demonstration of lesson one ("Parity magic") being taught is available here: + +{video url="https://www.youtube.com/embed/FnwBratAhfg"} + +{panel end} + +{panel type="general" title="Notes on resources"} + +{image file-path="img/topics/parity-cards.png" alt="A pile of square cards with black on one side and white on the other side."} + +You will require: + +- A set of 36 "fridge magnet" cards, all identical with a different colour on + each side (e.g. black and white); or non-magnetic cards, in which case the + demonstration should be done on a table-top or the floor. + The magnetic ones would need to be magnetic both ways up; sheets of + double-sided magnetic material can be purchased, but conventional fridge + magnets usually won't stick upside down. + Double sided magnetic cards can also be made by sticking single-sided + magnetic sheet back to back. + Paper (non-magnetic) cards can be made by cutting up a sheet of light card + that is a different colour on each side. +- A metal board (ideally a whiteboard) if magnetic cards are being used. +- Each pair of children will need: a set of 36 (non-magnetic) cards as above. + +There is also an +[online interactive version of the parity cards here](http://csfieldguide.org.nz/en/interactives/parity/index.html), +from the Computer Science Field Guide. + +{panel end} + +## Key questions + +- Why is it important for computers to be able to detect if the data received + over the internet is the same as the data that was sent? +- What if I sent you an email that said you could now have Monday off school, + but when you received it, there was some electrical interference and a + **bit** was changed from off to on so that the word "now" became "not". + What would your reaction be? +- Can computers correct these sorts of mistakes automatically, and how would + they do that? + +### Potential answers could include: + +Students may come up with situations where computers won't read a disc or scan a code, but they may also confuse this kind of problem with other kinds of failures such as an error in a program or a flat battery. + +{image file-path="img/topics/school-test-error.png" alt="A school test shows every question marked correct but the overall score is 0%."} + +Adults use computers for important things like banking, writing school reports, and communicating with each other. +If the information being stored got changed without anyone knowing, you'd get the wrong balance in your account (too much or too little), the wrong grade in your report, or the wrong message in an email. +Or worst still the website you are wanting to go to for your learning or the DVD you want to play won’t work! +This activity will look at how computers correct this automatically. + +## Lesson starter + +1. Teacher to class: "I’ve just learnt a magic trick I want to show you". + +2. Teacher to class: "So who will be my assistant?" + +3. Teacher to student: Hand the cards to the student and ask them to make a + grid of 5 by 5 cards (calling it "5 rows of 5 cards might be clearer for + younger students"). + "Don't make any patterns - try to make it as random as possible". + To speed up the setup you could have two students do this task; encourage + them to leave a small gap between the cards, and not be too fussy about + making them straight. + +4. Point out that the cards represent bits (binary digits). + If they haven't studied binary numbers, you may need to just point out that + this is the way everything is represented on computers - the cards here + could represent a file, a message, a web page or even a program. + +5. Teacher to class: "I’m going to make this a little bit harder by adding + another row and another column". + +{panel type="teaching" title="Teaching observations"} + +Of course, you are doing this on purpose because what you want to do is make +sure that the number of black sides showing in each row and column is an even +number. +This is always possible; if the student put an odd number of black cards in a +row, you add another black card to the row; if they put an even number in the +row, add a white card, to keep it as an even number. +Remember that zero is an even number. + +The extra cards are called "parity bits" (or parity cards), but there's no +need to introduce the terminology yet, since that reveals the "magic"; for the +meantime the idea is for the class to think that you are just adding even more +random cards to make the task harder. + +You should practise this several times before doing it in front of the class, +as it becomes a lot easier to make it look casual when you've done it before, +and makes the trick more mysterious. + +{panel end} + +{image file-path="img/topics/parity-cards-6x6-grid-step-1.png" alt="5 rows of 5 parity cards in a random arrangement." caption="Step 1: Example layout of a 5x5 grid set up by the volunteer."} + +### Step by step adding a parity bit to each row and column + +{image file-path="img/topics/parity-cards-6x6-grid-step-2.png" alt="Progress image of adding parity bits." caption="Step 2: Adding parity bit for the first row."} + +{image file-path="img/topics/parity-cards-6x6-grid-step-3.png" alt="Progress image of adding parity bits." caption="Step 3: Adding parity bit for the second row."} + +{image file-path="img/topics/parity-cards-6x6-grid-step-4.png" alt="Progress image of adding parity bits." caption="Step 4: Adding parity bit for the third row."} + +{image file-path="img/topics/parity-cards-6x6-grid-step-5.png" alt="Progress image of adding parity bits." caption="Step 5: Adding parity bit for the fourth row."} + +{image file-path="img/topics/parity-cards-6x6-grid-step-6.png" alt="Progress image of adding parity bits." caption="Step 6: Adding parity bit for the fifth row."} + +{image file-path="img/topics/parity-cards-6x6-grid-step-7.png" alt="Progress image of adding parity bits." caption="Step 7: Adding parity bit for the first column."} + +{image file-path="img/topics/parity-cards-6x6-grid-step-8.png" alt="Progress image of adding parity bits." caption="Step 8: Adding parity bit for the second column."} + +{image file-path="img/topics/parity-cards-6x6-grid-step-9.png" alt="Progress image of adding parity bits." caption="Step 9: Adding parity bit for the third column."} + +{image file-path="img/topics/parity-cards-6x6-grid-step-10.png" alt="Progress image of adding parity bits." caption="Step 10: Adding parity bit for the fourth column."} + +{image file-path="img/topics/parity-cards-6x6-grid-step-11.png" alt="Progress image of adding parity bits." caption="Step 11: Adding parity bit for the fifth column."} + +{image file-path="img/topics/parity-cards-6x6-grid-step-12.png" alt="Progress image of adding parity bits." caption="Step 12: Adding parity bit for the sixth column."} + +The last parity bit placed is useful because it will always work for both the +column and row; if it doesn't match for both the row and column then you'll +have made a mistake with one of the cards, and should go back and check them +(try to not make it obvious that you're doing that). + +{panel type="teaching" title="Teaching observations"} + +Now you have added parity cards to make the number of black squares in every +row and every column equal to an even number. +Don't point this out to the students at this stage. + +{panel end} + +## Lesson activities + +Teacher to student: "I'd like you flip over one card while I cover my eyes". + +Teacher to class: "Keep a close eye on which card it is to check if I have done +my magic trick correctly!" + +{panel type="teaching" title="Teaching observations"} + +You need to emphasise that you want just one card flipped over. +This prevents them from turning over too many cards (or none!). +Usually students will follow this instruction, and their classmates will be +able to confirm to you that, or let you know if one hasn't been turned over +yet. + +{panel end} + +After the class confirms that a single card has been flipped, turn around to +look at the cards. +Scan the cards looking for the row with an odd number of black squares, and the +column that has an odd number of black squares. +The card that has been flipped will be at the intersection of these two lines. +Turn this card over casually to restore it to the correct colour, saying “it’s +this one”. + +You can make a fuss that it might have been a fluke, so repeat the trick again. +(After you put the card back to how it was originally, look away again and ask +for another card to be flipped over.) + +{image file-path="img/topics/parity-wizard.png" alt="A wizard holding a magic one with parity cards on it."} + +So is it magic? Or is it a trick? + +Teacher to class: "Let’s first look at the cards before one was turned over" +(make sure you've restored the card that was just flipped). +The following steps will help the students to uncover what you've done: + +- "Are there any patterns you can see? Think, pair, share". +- "Let’s break it down into parts". +- "Let’s look at the first row - count the black squares - how many are + there?" 4 (in the example above). +- "Now the second row - count the black squares - how many are there?" 2. +- Note that it could be a row of all white squares - which would be 0 black + squares; or it could be a row of all black squares - 6. + +Teacher to class: “What do these numbers have in common?”. +Students should be able to observe that they are all even numbers. +If they need a hint, you could ask if there are any odd numbers there. + +Let’s now look at the columns: + +- "Does the first column have the same rule as the rows?". +- "What about the other columns?". + +Teacher to class: “So how did we end up with an even number in every row and +column - did the volunteer choose that?” (they didn't!). + +Remove your extra row and column, and have them explain what colour to place at +the end of the first row to make sure there is an even number of black cards. +For example, if there are 3 black cards, ask what colour you need to add to +make it into an even number (black)? +If there are 4 black cards, they should work out that you need a white card to +keep it even. + +Continue doing this for each row; then do it for the columns. +This is a good exercise for students thinking about even and odd numbers. +For the last (corner) card, ask if you should use the row or column to decide +it. +They should observe that it's the same for both. + +Once the extra row and column have been added, ask “So what happens when I turn +a card over from black to white?” (It reduces the number of black cards by one, +so it's now an odd number). +"What if I change a white card to black?" (it adds one, which also gives an odd +number). + +The students might work out from this how to find the flipped card, but in +either case, have a student come up, and ask them to look away while you flip +a card. +Then, when they look back, ask "Is the first row ok?" (They should notice that +it's still even, so hasn't been changed). +Carry on for each row until they identify the one with an odd number of black +cards. +Draw a box around that row, and say "So one of these cards was flipped?". +Now do the same with the columns - ask if each one is correct, then draw around +the column that they identify. + +Now ask "So which card was flipped?" Usually students will identify the one at +the intersection. + +{panel type="teaching" title="Teaching observations"} + +Students have now worked out for themselves how this works. +The key idea is that we just added a little more data, but could reconstruct +the original if one card was changed. + +{panel end} + +{panel type="math" title="Mathematical links"} + +This lesson would be useful for students learning about what even and odd +numbers are. + +It also exercises the idea of grids (you can use phrases like "5 by 5") and +the language of columns and rows. + +{panel end} + +## Applying what we have just learnt + +- This kind of method is applied to almost all data that is stored or + transmitted on computers (although usually a more sophisticated method is + used that is even more reliable). + If we didn’t have error detection and correction then unexpected errors in + data would be common, and digital devices wouldn't be used to store + anything important. + The world would be in chaos and people wouldn’t trust computers. + Computers wouldn’t be reliable. +- DVDs and CDs wouldn’t work if one fleck of dust was on the disc. +- Backing up wouldn't help much as this would also be unreliable too. +- Transmitting data over long distances (e.g. from space probes) would be + particularly unreliable, since it can take minutes, or even days, for data + to arrive, and it's not feasible to request it to be retransmitted if it + has had interference. + +## Lesson reflection + +Try using other objects. +Anything that has two easily distinguished 'states' is suitable. +For example, you could use playing cards, coins (heads or tails) or cards with +0 or 1 printed on them (to relate to the binary number system). + +What happens if two, or more, cards are flipped? +(It is not possible to detect exactly which two cards were flipped, although +it is possible to tell that something has been changed. +You can usually narrow it down to one of two pairs of cards. +With 4 flips it is possible that all the parity bits will be correct +afterwards, and so the error could go undetected.) + +Another interesting exercise is to consider the lower right-hand card. +If you choose it to be the correct one for the column above, then will it be +correct for the row to its left? (The answer is yes, always. +In this card exercise we have used even parity—using an even number of black +cards. +Can we do it with odd parity? +(This is possible, but the lower right-hand card only works out the same for +its row and column if the numbers of rows and columns are both even or both +odd. +For example, a 5 × 9 layout will work fine, or a 4 × 6, but a 3 × 4 layout +won’t.) diff --git a/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/product-code-check-digits-ct-links.md b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/product-code-check-digits-ct-links.md new file mode 100644 index 000000000..ed5584dcc --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/product-code-check-digits-ct-links.md @@ -0,0 +1,121 @@ +{panel type="ct-algorithm" title="Algorithmic thinking"} + +The steps we follow to calculate the barcode checksum is an Algorithm. +We follow this same Algorithm every time we want to create a checksum for a +barcode, and every time we want to check if there is an error in a barcode. +It specifies exactly how we should do this, and students practice algorithmic +thinking as they follow and articulate this algorithm. + +#### Examples of what you could look for: + +Can your students write instructions that allow someone else to calculate the +barcode checksum of different products with the same number of digits in their +barcodes (e.g. 12)? +What about instructions that can be used for any different product, regardless +of the number of digits? + +{panel end} + +{panel type="ct-abstraction" title="Abstraction"} + +When a barcode goes through a scanner the scanner reads the black and white +bars on the barcode, whereas when a human looks at a barcode they will read +the digits. +The numbers on the barcode and the black and white bars both represent the +same thing though - a numeric code that identifies a product, and the bars +are an abstracted representation of these numbers. + +When we are calculating the checksum we also don’t need to think about what +product it actually represents is, we just want to find out whether or not +the barcode is correct and all we need to do that is the barcode itself. + +#### Examples of what you could look for: + +Can your students explain how both of these represent the product? +Can they explain the reason for the different representations? + +{panel end} + +{panel type="ct-decomposition" title="Decomposition"} + +To check the product code, the task of calculating the checksum is broken +down into individual steps: adding one number at a time; doing that for +each subset of digits. +By doing this we are decomposing the problem into smaller subtasks which are +easier to solve than the overall problem of “Is this barcode correct?”. +Each time we solve one of these subtasks we move on to the next and move +closer to a solution. + +#### Examples of what you could look for: + +Are students able to independently break the calculation into the two halves, +and then perform the additions on each group of values, and combine them +to get the checksum? +Can they do this for a range of different codes with different errors in them? + +{panel end} + +{panel type="ct-pattern" title="Generalising and patterns"} + +The same method can apply to shorter (e.g. 8-digit) and longer product codes. +If you look at some courier packages you might see much larger bar codes, +and these use similar error detection techniques. +To check each of these bar codes we follow the same algorithmic pattern of +using the numbers on the code (except for the last one) and applying a +mathematical formula to these to get the final digit (or what it should be +if there are no errors!). + +#### Examples of what you could look for: + +Can students apply the algorithm (the version starting at the right hand end) +to different length product codes? +Do they appreciate how it still checks the digits in each case? +(You can find different codes by searching for images of barcodes online). + +{panel end} + +{panel type="ct-evaluation" title="Evaluation"} + +This error detecting process isn't perfect. +To evaluate its effectiveness we need to thoroughly test it. +Ask students what the different ways we need to test the algorithm are. +Interestingly it is not enough to just test it with a bunch of different +barcodes with one number in them changed, we need to test a range of +different types of errors as well! +For example what if instead of one number changing two of the numbers are +switched around? +What if two numbers are changed? +Are there any situations where we couldn’t figure out if something had changed? + +This is an example of an important technique used when evaluating algorithms +and programs, called edge case testing. + +#### Examples of what you could look for: + +Can students demonstrate what kind of errors the checksum is guaranteed +to detect? +Can they give examples of errors that a human might make when typing in a +number that wouldn't be detected? +Can they work out how likely it is that an error will go undetected in each +of these cases? +They can do this by trial and error or by logical reasoning. + +{panel end} + +{panel type="ct-logic" title="Logic"} + +The algorithm is designed to be sensitive to the type of errors that are +most likely to happen. + +We can also evaluate the algorithm using logical thinking. + +#### Examples of what you could look for: + +Can students explain the logic that was applied in the process of +designing a barcode checksum, and especially the choice to multiply some +of the numbers by 3? + +In cases where the checksum is unable to detect and error can students +explain logically why the error cannot be detected? + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/product-code-check-digits-programming.md b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/product-code-check-digits-programming.md new file mode 100644 index 000000000..b19525089 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/product-code-check-digits-programming.md @@ -0,0 +1,5 @@ +These challenges provide students with the opportunity to exercise the ideas they have been working with around the checksums on product codes. +Before assigning the challenges, you will need to check on which type of product code is common locally (particularly 12 or 13 digit), as the initial exercises are specific to the type of product code, to simplify the programming. +12-digit product codes, which are common in North America; 13-digit product codes are more common in Europe, Australia and New Zealand. + +The challenges scaffold writing a program that does the basic checksum for a code, but students could enhance their program to put in extra checks, such as only digits (not letters) being typed in, and having the correct number of digits. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/product-code-check-digits.md b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/product-code-check-digits.md new file mode 100644 index 000000000..c3bb4d27d --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/lessons/product-code-check-digits.md @@ -0,0 +1,258 @@ +# Product code check digits + +{panel type="general" title="Preparatory knowledge"} + +It is helpful, but not essential, for students to have done the lesson on the +Modulo operator before doing this lesson. + +{panel end} + +## Key questions + +How many people do you know check their dockets at the supermarket or in a shop to be sure that what they purchased matches the docket? + +## Potential answers could include: + +- Typically it’s not many, and that is because the barcode system is so reliable that we trust that it works. + +## Lesson starter + +{image file-path="img/topics/error-correction-paint-tin.png" alt="A black paint tin with paint across the name and barcode."} + +Explain to your class that you can calculate what the last digit will be on a +12 or 13 digit product code (the number on a bar code). +Have someone find a barcode on a product for you (e.g. some stationery or food +item) and tell you all the digits except the last one (sometimes the digits +have gaps or are before and after the bar code, so they may need to read +carefully to get all digits in the right order; also, some small items have +8-digit codes, and it's best to avoid these for now). + +If 13-digit codes are common in your country, use the following guide, +otherwise skip to the 12-digit guide below. + +## Lesson activities + +### 13-digit instructions + +Here’s an example of how to calculate the last digit on a 13-digit barcode. +It's a somewhat odd process of adding and multiplying numbers, but the same +formula will always give you the correct value for the 13th digit, as long as +there isn't an error in the number! + +{image file-path="img/topics/barcode-13.jpg"} + +The above example barcode is from a product that a student might have selected, +so they would give you these 12 digits (they should keep the 13th digit +secret): + +{image file-path="img/topics/barcode-13-step-1.png"} + +However, instead of writing it out as above, write it on the board with every +second digit on alternating lines: + +{image file-path="img/topics/barcode-13-step-2.png"} + +What you are doing is writing the numbers in an odd position at the top and +the even positions in the second row. + +Now add up all the numbers in the first row (= 29) and take the number in +the ones column only. +(Later we will introduce a shortcut, where you only need to keep the last +digit after each addition e.g. 9+5 gives 4; for older classes you could +introduce this now through the lesson on modulo arithmetic. +Using only the last digit can seem too easy to them, and can challenge their +thinking!) + +{image file-path="img/topics/barcode-13-step-3.png"} + +Next add up all the numbers in the second row (to get 24), and again take the +number in the ones column only. + +{image file-path="img/topics/barcode-13-step-4.png"} + +Multiply the digit from the second sum by three i.e. the 4 in the 24 is +multiplied by 3, but take the ones column answer only (the 2). +With 13-digit barcodes, the units digit from the second row sum is always +multiplied by three. + +{image file-path="img/topics/barcode-13-step-5.png"} + +Add the result from the first sum to the multiplied answer (in this case, + the 9 from the 29 to the 2 from the 12): + +{image file-path="img/topics/barcode-13-step-6.png"} + +Once again, we only need the last digit; in the example it's a 1. +Now ask what you need to add to that digit to get a 0 in the ones column (i.e. +it will add up to either 0 or 10). +In this case, we ask what plus 1 = 10? +The answer, 9, gives us the checksum, which is the 13th number in the product +code. +If the brown total above had ended with a 0, then the checksum would be 0 +(since that adds to give a 0 in the ones digit). + +{image file-path="img/topics/barcode-13-step-7.png"} + +This number should be the final digit on the product code. +Ask the student if you got the right one. +Of course, at this stage there's a small chance you guessed it, but now they +can explore the codes themselves, and they'll soon find out that it always +works if the number is copied correctly. + +{panel type="teaching" title="Teaching observations"} + +Check that students notice to multiply the second line by 3 and take the last +digit of that number. +If the final number doesn't match the one that was expected, then the product +code had an error in it (which is a nice illustration of error detection at +work - either you wrote it wrong, or the student read it out incorrectly), +or you had an error in the calculations (which wouldn't happen on a computer, +but at least it provides a chance for the class to use their basic maths +facts!). + +{panel end} + +### 12-digit instructions + +Here’s an example of how to calculate the last digit on a 12-digit barcode. +It's a somewhat odd process of adding and multiplying numbers, but the same +formula will always give you the correct value for the 12th digit, as long as +there isn't an error in the number! + +{image file-path="img/topics/barcode-12.jpg"} + +The above example barcode is from a product that a student might have +selected, so they would give you these 11 digits: + +{image file-path="img/topics/barcode-12-step-1.png"} + +However, instead of writing as above, write it on the board with every +second digit on alternating lines: + +{image file-path="img/topics/barcode-12-step-2.png"} + +What you are doing is writing the numbers in an odd position at top and the +even positions in the second row. + +Now add up all the numbers in the first row by drawing plus signs between +each digit (in the example this will give 17), and take the number in the +ones column only. +(Later we will introduce a shortcut, where you only need to keep the last + digit after each addition e.g. 6+4 gives 0; for older classes you could + introduce this now through the lesson on modulo arithmetic. +Using only the last digit can seem too easy to them, and can challenge +their thinking!) + +{image file-path="img/topics/barcode-12-step-3.png"} + +Next add up all the numbers in the second row (to get 27), and again take +the number in the ones column only. + +{image file-path="img/topics/barcode-12-step-4.png"} + +Multiply the digit from the first sum by three i.e. the 7 in the 17 is +multiplied by 3, but take the ones column answer only (the 1 from the 21). +With 12-digit barcodes, the units digit from the first row sum is always +multiplied by three. + +{image file-path="img/topics/barcode-12-step-5.png"} + +Add the multiplied answer to the second sum (in this case, the 9 from the +29 to the 7 from the 27): + +{image file-path="img/topics/barcode-12-step-6.png"} + +Once again, we only need the last digit; in the example it's a single +digit already (8). +Now ask what you need to add to that digit to get a 0 in the ones column +(i.e. it will add up to either 0 or 10). +In this case, we ask what plus 8 = 10? +The answer, 2, gives us the checksum, which is the 12th number in +the product code. +If the brown total above had ended with a 0, then the check digit would be +0 (since that adds to give a 0 in the ones digit). + +{image file-path="img/topics/barcode-12-step-7.png"} + +This number should be the final digit on the product code. +Ask the student if you got the right one. +Of course, at this stage there's a small chance you guessed it, but now +they can explore the codes themselves, and they'll soon find out that it +always works if the number is copied correctly. + +{panel type="teaching" title="Teaching observations"} + +Check that students notice to multiply the first line by 3 and take the +last digit of that number. +If the final number doesn't match the one that was expected, then the +product code had an error in it (which is a nice illustration of error +detection at work - either you wrote it wrong, or the student read it +out incorrectly), or you had an error in the calculations (which wouldn't +happen on a computer, but at least it provides a chance for the class to use +their basic maths facts!) + +{panel end} + +## Checking a barcode + +In the activity above we worked out the value of the final digit. +When a barcode is scanned, an easy way to check the digits is to add the check +digit to the total, and make sure that the sum ends with a 0 +(since that's how the check digit was calculated). + +This time you can ask students to check some product codes - ask them to +choose a product with a product code on it (typically a stationery item around +the room will be suitable; many books have 13-digit product codes that +are suitable too). +To simplify how we think about this, we will now express it by adding the +values up backwards. +(This approach will work for both 12- and 13-digit barcodes). + +Have students start at the end of the barcode, and write down every second +digit (starting with the last one). +That line is added up (you only need to keep the last digit, but out of + interest, keep the whole sum). + +Then write down every second digit starting at the second to last one. +These digits are summed and multiplied by three, again keeping the whole sum. + +Now add the two sums together. If the digits are correct then the total should +sum to a number ending with 0. + +Try out different product codes to check that they sum to a number ending in +zero (or if you keep only the last digit from each calculation along the way, +the final value will be zero.) + +Now try changing one digit in a valid product code, and see if the total is +non-zero i.e. you can detect the error. +Is there any single digit that you can change in a product code that will +still result in the zero as the total? (This won't be possible). +Are there any two digits that you can change that will give a zero total? +(If one digit change happens to counteract another one, then this can happen, +but it's a rare situation). +If two wrong digits do happen to counteract each other, the error will go +undetected, although the product code will be so incorrect that when the +computer goes to look it up, chances are it's not going to find a product with +that number anyway. + +Have students check more product codes. Do they all add up to a multiple of 10? + +## Applying what we have just learnt + +There needs to be a system around the checksum to make sure that the people +using it know when the checksum worked and when it didn’t. +How do checkout systems let the operator know if there was an error in the +scanning? +(Typically with an error sound). + +If the packaging has been damaged or is bent, it may not be possible to read +the bars accurately (it keeps giving errors), so the operator has to type in +the numbers. + +What kind of errors can be made if a person types in the numbers? +The main type of answers to look for are: + +- A digit has its value changed. +- Two adjacent digits are swapped with each other. +- A digit is inserted in the number. +- A digit is removed from the number. diff --git a/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/unit-plan-ct-links.md b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/unit-plan-ct-links.md new file mode 100644 index 000000000..7b4465eec --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/unit-plan-ct-links.md @@ -0,0 +1,88 @@ +{panel type="ct-algorithm" title="Algorithmic thinking"} + +How to detect and correct errors in data is a very important problem in Computer +Science, and to solve this problem we need algorithms. +Asking “Has this barcode been scanned correctly?” has a yes or no answer, and +means you can perform error control on that specific barcode, but it is not a +solution to the bigger problem of “How do we perform error control for +barcodes?”. +The solution to that problem is an algorithm, and if we use that algorithm we +can check any barcode we are given and find out if it is correct or not. + +{panel end} + +{panel type="ct-abstraction" title="Abstraction"} + +When we perform error control there are only some details we need to focus on to +perform this task, and many others can be ignored by using abstraction when we +look at the problem. +With error control, we only care about what the bits or numbers we are looking +at are, and we don’t need to know what those bits and numbers actually represent +or mean - we don’t need to know if they are the numbers and check digit on the +barcode for a loaf of bread, or if the bits we are checking for even parity +represent a video stored on a laptop. +This information can be discarded because it is irrelevant to the task of error +control. +Similarly, if we have performed error control on our piece of data and now we +are using it for something, we no longer need to think about how that error +detection and correction worked; it is usually hidden from the user, so all they +see is data that seems to be stored and transmitted accurately. +The designer of such a system needs to know error control is enabling the data +to get through accurately, but once that is happening, they can put all the +information about how it is happening to one side and focus on working with the +data. + +{panel end} + +{panel type="ct-decomposition" title="Decomposition"} + +To solve our problem of performing error control we need to break this problem +down into smaller components. +The instruction “Detect the error in this data and correct it” involves many +steps and can’t be solved all at once! +First it needs to be broken down into smaller steps and then each of these can +be solved. + +{panel end} + +{panel type="ct-pattern" title="Generalising and patterns"} + +Detecting and correcting errors is a very common problem in Computer Science, +and it relates to data validation in general, which is important for security +and encryption as well. +Seeing the similarities between each of these problems allows us to generalise +the algorithms we use to multiple situations where we need to find out if data +has been changed or not. +For example, the simple single-digit checksum used on product codes can be +generalised to multiple-digit checksums used for some ID numbers, through to +checksums that are dozens of digits long that are used to check the downloaded +files have arrived reliably. + +{panel end} + +{panel type="ct-evaluation" title="Evaluation"} + +We can evaluate our solutions by testing them with a range of different inputs. +Does our solution detect when data has an error in it or not? +What happens if there is more than one error in our data? +Students can test the algorithms with many different inputs to evaluate how good +(or not good!) they are. +We can also evaluate our algorithms like the ones in this unit, and show that +they will always work by constructing a mathematical proof, or through logical +reasoning, which ties in with the next Computational Thinking skill: logic. + +{panel end} + +{panel type="ct-logic" title="Logic"} + +If we have even parity in the magic trick, why will flipping a card always cause +the number of white cards to become odd? +There is a logical reason for this and getting students to articulate that this +is because of the relationship between even and odd numbers is a way to exercise +their logical reasoning and maths understanding. +If we know that the data in our parity trick grid should have even parity, how +do we determine which card has been flipped? +We can follow an algorithm to do this, but we need to use our logic skills to +construct this algorithm and understand why it works. + +{panel end} diff --git a/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/unit-plan.md b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/unit-plan.md new file mode 100644 index 000000000..59c40f171 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/unit-plan.md @@ -0,0 +1,181 @@ +# Error detection and correction unit plan + +{panel type="teaching" title="See teaching this in action!"} + +A demonstration of lesson one ("Parity magic") being taught is available here: + +{video url="https://www.youtube.com/embed/FnwBratAhfg"} + +{panel end} + +## What’s it all about? + +{image file-path="img/topics/parity-trick-example.png" alt="A student is surprised another student can detect the changed card in the Parity Trick."} + +The world is a complicated and imperfect place, and errors can occur whenever +information is stored or transmitted. +Data stored on hard disks, DVDs and flash memory can be changed if there is a +tiny fault in the device (and these occur regularly!). +Information received over networks can be corrupted if there's interference on +the line or a faulty component in the system. +Even scanning information from barcodes and QR codes is a form of information +transmission, and small errors such as dirt or scratches on the code can change +the information. +Yet we rely on data so much that there could be serious implications from even +a single digit error in a student's grade, or a small change to a payment, or +an incorrect reading in a medical scan. +Error detection techniques add extra information to data to determine when +errors have occurred. +The extra information might be an extra "check digit" such as the last digit of +a credit card number or barcode number on a product, or extra binary digits +(bits) in data stored on a computer. + +Not only can most digital systems detect errors, but many can correct them as +well, back to what the data should have been. +Error correction can appear to be magic, since it involves being able to put +data back to how it was originally, even when you don’t know what the original +data was. +In fact, the first lesson plan presents a technique called "parity error +correction" as a magic trick that most audiences find intriguing. +In the trick the demonstrator is “magically” able to figure which one out of +dozens of cards has been turned over, using the same kind of method that +computers use to figure out when and where an error has occurred in a piece of +data. + +{image file-path="img/topics/mug-with-barcode.png"} + +A related technique is used on the barcodes printed on products to check that +they are scanned correctly at a checkout; the last digit in the product code is +based on a mathematical combination of all the other digits, and can easily be +calculated from them. +If the calculation comes up with a different value for the last digit, it's a +warning that one of the digits is wrong, in which case the scanner gives a +warning, and the operator might have to scan the item again, or type in the +number, or look it up some other way. + +The lessons in this unit show how errors can be detected, and in same cases, +corrected to restore the original data. +It also enables students to explore how we use some relatively simple ideas to +make our digital systems so reliable that people using them don't realise that +this is all happening underneath the surface! + +## Digital Technologies | Data Representation + +When data is stored on a disk or transmitted from one computer to another, we +usually assume that it doesn’t get changed in the process. +We are going to learn about some ways in which computers make sure that +information can be retrieved reliably even when errors have occurred. +The digital nature of the data (i.e. it is made up of digits) is what allows us +to detect and correct these errors. + +## Vocabulary Explained + +**Error detection** is a method that can look at some data and detect if it has +been corrupted while it was stored or transmitted. + +**Error correction** is a step better than error detection; when it detects an +error it tries to put the data back to how it should have been. + +**Error control** is the general term for error correction and error detection +systems. + +{image file-path="img/topics/odd-parroty-parity.png"} + +“**Parity**” often comes up in error control as there is a well-known method +based on it. +The word "parity" has a general meaning of simply saying if a number is even or +odd. +It comes from the same root word as “pair” – **even parity** means that there is +an even number of objects (they can be put in pairs), and **odd parity** means +they can’t be put into pairs. +If you have 5 white socks, then they have odd parity, but you want them to have +even parity! +The system described in lesson plan one uses even parity, as it is slightly +easier to work with in this situation; so "even parity" is just a fancy way of +saying that there is an even number of something. + +A "**check digit**" is an extra digit added to the end of an important number +such as a credit card number, product code (bar code), identity number, tax +number, or a passport number, that can be used to check if the number has been +typed in correctly. +In some situations more than one digit is used, in which case it is referred to +as a checksum. + +{panel type="math" title="Mathematical links"} + +- Even and odd numbers. +- That even and odd numbers alternate (adding or subtracting one from a number + changes it from even to odd, or from odd to even). + Mathematically, if *N* is even, then *N + 1* and *N -1* are odd. +- Basic facts knowledge (adding, multiplying). +- Clock arithmetic (can also be presented as remainder from division, or the + modulo operator). + +{panel end} + +## Real world implications + +{image file-path="img/topics/arrest-mugshot-for-typo.png"} + +Imagine that you send an email to an online trader saying that you will pay $20 +for their product. +But suppose some interference occurs along the way, and the $20 is changed to +$80. +The trader will probably be very happy to accept your offer, and charge you 4 +times as much for the product as you wanted to pay! +Or what if someone types in a credit card number to buy something, but gets one +digit wrong? +Someone else might get charged for the item and person who mis-typed the number +might be accused of fraud just because of a simple typing mistake. + +{image file-path="img/topics/cd-with-marks.png"} + +Everything stored by computers and sent between them is represented as bits +(binary digits). +It is easy for these to be changed accidentally because of errors in the devices +that are storing or transmitting them. +A **CD** might get a scratch or a piece of dust on it that changes a zero into a +one or vice versa. +A hard **disk** might have the magnetism accidentally fade where a bit (binary +digit) is stored. +On the Internet, interference and bad connections can cause bits to be altered. +When scanning printed information such as barcodes and QR codes there might be +ice or dirt on the product that causes the wrong value to be scanned. +So how come we don’t have to worry about this? + +And even worse, what if we detect that there's been an error in the data, but +can't get a new copy? +For example, if data is received from a deep space probe, it would be very +tedious to wait for retransmission if an error has occurred and we can’t fix it! +(It takes just over half an hour to get a radio signal from Jupiter when it is +at its closest to Earth!) +And when you read data from a computer's file system, if an error is detected +you can't go back in time and save a new copy (well, making a backup is like +anticipating that you'll need to go back in time one day, but it's not always +convenient or easy to remember to use a backup). +We need to be able to recognize when the data has been corrupted (error +detection) and ideally we also need to be able to reconstruct the original data (error correction). + +This was a serious problem on early computers, so scientists soon invented +methods to allow computers to detect errors in data and correct those errors. +We will learn about one way to do this using a method that is called **parity**. +But instead of using zero and one bits inside a computer system, we’ll use +cards with two sides, and do it as a magic trick. +More sophisticated versions of this are widely used on modern storage and +transmission devices to make sure that typical minor hardware problems are +unlikely to result in a major loss of data. +The more complex error control systems used on modern digital devices are able +to detect and correct multiple errors. +The hard disk in a computer has a large amount of its space allocated to +correcting errors so that it will work reliably even if parts of the disk +surface fail. +The activities here will show how adding extra information (without going to the +trouble of making a whole backup, which would use twice the space) provides a +good level of resilience against errors. + +## Reflection questions + +- What was most surprising about the learning that happened from the teaching of this unit? +- Who were the students who were very systematic in their activities? +- Who were the students who were very detailed in their activities? +- What would I change in my delivery of this unit? diff --git a/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/unit-plan.yaml b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/unit-plan.yaml new file mode 100644 index 000000000..4c211f842 --- /dev/null +++ b/csunplugged/topics/content/en/error-detection-and-correction/unit-plan/unit-plan.yaml @@ -0,0 +1,9 @@ +lessons: lessons/lessons.yaml +computational-thinking-links: unit-plan-ct-links.md + +age-groups: + 8-10: + parity-magic: + number: 1 + product-code-check-digits: + number: 2 diff --git a/csunplugged/topics/content/en/glossary/algorithm.md b/csunplugged/topics/content/en/glossary/algorithm.md new file mode 100644 index 000000000..98bba7a57 --- /dev/null +++ b/csunplugged/topics/content/en/glossary/algorithm.md @@ -0,0 +1,3 @@ +# Algorithm + +A step by step process that describes how to solve a problem and/or complete a task, which will always give a result. diff --git a/csunplugged/topics/content/en/glossary/pixel.md b/csunplugged/topics/content/en/glossary/pixel.md new file mode 100644 index 000000000..679c40a93 --- /dev/null +++ b/csunplugged/topics/content/en/glossary/pixel.md @@ -0,0 +1,3 @@ +# Pixel + +This term is an abbreviation of picture element, the name given to the tiny squares that make up a grid that is used to represent images on a computer. diff --git a/csunplugged/topics/content/en/learning-outcomes.yaml b/csunplugged/topics/content/en/learning-outcomes.yaml new file mode 100644 index 000000000..c77169341 --- /dev/null +++ b/csunplugged/topics/content/en/learning-outcomes.yaml @@ -0,0 +1,320 @@ +# BINARY NUMBERS LEARNING OUTCOMES + +binary-convert-decimal: + text: Perform a demonstration of how the binary number system works by converting any decimal number into a binary number. + curriculum-areas: + - algorithmic-thinking + +binary-justify-zeros-and-ones: + text: Justify why there aren’t actual 0’s and 1’s zooming around inside a computer. + curriculum-areas: + - abstraction + +binary-argue-zeros-and-ones-stored: + text: Argue that 0’s and 1’s are still a correct way to explain what is stored in the computer. + curriculum-areas: + - abstraction + +binary-explain-different-states: + text: Explain why we can use any two different states or things to represent binary; it doesn’t have to just be 0’s and 1’s. + curriculum-areas: + - abstraction + +binary-explain-logic-one-bit: + text: Explain the logic of why the right-hand bit needs to represent a one. + curriculum-areas: + - logic + +binary-explain-off-on: + text: Explain why a particular card (bit) must be turned off or on for a given number. + curriculum-areas: + - logic + +binary-predict-bit-value: + text: Predict what value the next card (bit) e.g. the last bit value is 16, what is the next one? + curriculum-areas: + - generalising-and-patterns + +binary-explain-doubling: + text: Explain why we only need one more bit to represent twice as many numbers. + curriculum-areas: + - logic + +binary-recognise-alphabet-bits: + text: Recognise how computers represent alphabet characters as bits using a simplified method. + curriculum-areas: + - logic + +binary-create-message-converting: + text: Create your own message by converting alphabet characters to decimal numbers then to binary. + curriculum-areas: + - decomposition + +binary-interpret-message-binary: + text: Interpret a message using binary. + curriculum-areas: + - generalising-and-patterns + +binary-explain-codes-alphabets: + text: Explain how codes for larger alphabets could be created that also include capital letters, punctuation, symbols and diacritics (e.g. macrons, accents) + curriculum-areas: + - generalising-and-patterns + +binary-make-play-instructions-binary: + text: Make a play that gives instructions on how to represent a number in binary + curriculum-areas: + - decomposition + - drama + +binary-identify-opposite-pairs: + text: Identify opposite pairs such as happy face and sad face + curriculum-areas: + - abstraction + - literacy + +# DRAMA LEARNING OUTCOMES + +drama-create-video-binary: + text: Create an instructional video outlining how to count using the binary number system. + curriculum-areas: + - drama + +# ENGLISH LEARNING OUTCOMES + +speaking-clear-instructions-binary-cards: + text: Give clear instructions to explain how the binary cards can show any number between 0 - 63. + curriculum-areas: + - speaking + +# ERROR DETECTION AND CORRRECTION LEARNING OUTCOMES + +error-describe-steps-to-find-card: + text: Describe the steps it takes to work out how to find the card that is turned over. + curriculum-areas: + - decomposition + +error-explain-each-card-bit: + text: Explain that each card is a bit and that the cards can represent data. + curriculum-areas: + - algorithmic-thinking + +error-explain-chose-parity-card: + text: Explain why they chose each extra bit (card) when setting up a parity column/row. + curriculum-areas: + - generalising-and-patterns + +error-explain-checksum-algorithm: + text: Explain why the checksum algorithm is so reliable. + curriculum-areas: + - logic + +# LANUGAGE LEARNING OUTCOMES + +language-discuss-storing-characters: + text: Discuss why it's important to be able to store more than the standard English alphabet. + curriculum-areas: + - language + +# NUMERACY LEARNING OUTCOMES + +numeracy-adding-given-number: + text: Add numbers to a given amount. + curriculum-areas: + - numeracy + +numeracy-identify-even-odd: + text: Identify even and odd numbers by explaining why the most right number is different to the others. + curriculum-areas: + - numeracy + +numeracy-discuss-count-zeros-ones: + text: Discuss why you can count up to any number by only using 0s and 1s. + curriculum-areas: + - numeracy + +numeracy-explain-squaring: + text: Explain how understanding how binary numbers increase supports your knowledge of place value. + curriculum-areas: + - numeracy + +numeracy-compare-base10-base2: + text: Compare the base value systems of base 10 (our number system) and base 2 (binary). + curriculum-areas: + - numeracy + +numeracy-identify-larger-number: + text: Identify which number is larger, when comparing two numbers. + curriculum-areas: + - numeracy + +numeracy-count-by-pointing: + text: Count accurately by pointing at each item. + curriculum-areas: + - numeracy + +numeracy-recognise-patterns: + text: Recognise patterns for numbers 1 through to 8. + curriculum-areas: + - numeracy + +numeracy-recognise-number-alphabet-order: + text: Recognise that the alphabet is in an order and so are numbers. + curriculum-areas: + - numeracy + +numeracy-explain-even-odd-parity-problem: + text: Explain how knowing what odd and evens numbers are means you can solve the parity problem. + curriculum-areas: + - numeracy + +numeracy-describe-rows-columns: + text: Describe what are rows and what are columns. + curriculum-areas: + - numeracy + +numeracy-discuss-changing-one-card-state: + text: Discuss why changing one card will change the state of the row and column which it is in. + curriculum-areas: + - numeracy + +numeracy-explain-modulo: + text: Explain how the modulo operator works. + curriculum-areas: + - numeracy + +numeracy-identify-modulo-everyday-life: + text: Identify where modulo occurs in everyday life. + curriculum-areas: + - measurement + - music + - numeracy + +numeracy-discuss-modulo-understanding-remainders: + text: Discuss how knowing how modulo works, supports your understanding of remainders. + curriculum-areas: + - numeracy + +# PROGRAMMING LEARNING OUTCOMES + +programming-explain-sequencing: + text: Explain how sequence works in programming + curriculum-areas: + - programming + +programming-explain-output: + text: Explain why a program gives the output that it does. + curriculum-areas: + - programming + +programming-explain-variable-name: + text: Explain why choosing a good name for a variable is important. + curriculum-areas: + - programming + +programming-describe-variables: + text: Describe how variables store values and how they are used in your program. + curriculum-areas: + - programming + +programming-outline-variable-updates: + text: Outline how storing values in a variable is updated because of the code you have used. + curriculum-areas: + - programming + +programming-describe-variable-output: + text: Describe how a variable can be displayed as an output. + curriculum-areas: + - programming + +programming-identify-loop: + text: Identify how and when to use a loop to repeat a set of instructions. + curriculum-areas: + - programming + +programming-explain-join-concatenate: + text: Explain how to concatenate (join) strings of characters together. + curriculum-areas: + - programming + +programming-explain-asking-input-end-user: + text: Explain what you need to consider if you are asking for an input from the end user. + curriculum-areas: + - programming + +programming-identify-if-statement: + text: Identify when to use an if statement to check a set of conditions. + curriculum-areas: + - programming + +programming-demonstrate-indexing: + text: Demonstrate how indexing to access a letter at the specified position in a string works. + curriculum-areas: + - programming + +programming-explain-set-and-change-statement: + text: Explain the difference between the set statement and the change statement when used to update the value of a variable. + curriculum-areas: + - programming + +programming-describe-variable-initial-value: + text: Describe why a variable should be set to the correct type of initial value. + curriculum-areas: + - programming + +programming-explain-mod: + text: Explain what the modulo operator does in your program. + curriculum-areas: + - programming + +programming-explain-differences-if-else-if-statements: + text: Explain the difference between an if-else statement and an if statement and how you have used them in your program. + curriculum-areas: + - programming + +programming-describe-nested-statement: + text: Describe how a nested statement works in relation to your program. + curriculum-areas: + - programming + +programming-identify-list-store-data: + text: Identify when to use a list to store a set of data. + curriculum-areas: + - programming + +# SORTING LEARNING OUTCOMES + +sorting-explain-parallel-algorithm: + text: Explain how a Sorting Network parallel algorithm works. + curriculum-areas: + - algorithmic-thinking + +sorting-use-selection-sort: + text: Use selection (data comparisons) to sort multiple items. + curriculum-areas: + - decomposition + +sorting-identify-number-range: + text: Identify which number comes before or after in any range of numbers + curriculum-areas: + - numeracy + +sorting-organise-objects-size: + text: Organise objects from smallest in size to largest in size. + curriculum-areas: + - numeracy + +sorting-recognise-sorting-method: + text: Recognise that a sorting method can be applied to any values that can be ordered. + curriculum-areas: + - generalising-and-patterns + +sorting-demonstrate-alphabet-knowledge: + text: Demonstrate how your alphabet knowledge supports ordering words or books. + curriculum-areas: + - literacy + +sorting-comparing-musical-pitch: + text: Compare musical pitch in musical notation and aurally. + curriculum-areas: + - music + diff --git a/csunplugged/topics/content/en/programming-challenges-structure.yaml b/csunplugged/topics/content/en/programming-challenges-structure.yaml new file mode 100644 index 000000000..557935523 --- /dev/null +++ b/csunplugged/topics/content/en/programming-challenges-structure.yaml @@ -0,0 +1,17 @@ +languages: + scratch: + name: Scratch + number: 1 + icon: img/scratch-cat.png + python: + name: Python + number: 2 + icon: img/python-logo.png + +difficulties: + 1: + name: Beginner + 2: + name: Growing Experience + 3: + name: Ready to Expand diff --git a/csunplugged/topics/content/en/sorting-networks/sorting-networks.md b/csunplugged/topics/content/en/sorting-networks/sorting-networks.md new file mode 100644 index 000000000..350fc8ca6 --- /dev/null +++ b/csunplugged/topics/content/en/sorting-networks/sorting-networks.md @@ -0,0 +1,12 @@ +# Sorting networks + +As consumers we expect computers to process information as quickly as possible +so that programs run faster, websites load faster, and we don’t have to spend a +long time watching progress bars and the spinning wheel that appears when +computers are thinking about something! +One way to increase the speed of a computer is to write programs that use fewer +computational steps (as shown in the lessons on sorting and searching +algorithms). +Another way to solve problems faster is to have several computers work on +different parts of the same task at the same time, which is what this unit +explores. diff --git a/csunplugged/topics/content/en/sorting-networks/sorting-networks.yaml b/csunplugged/topics/content/en/sorting-networks/sorting-networks.yaml new file mode 100644 index 000000000..d583aee91 --- /dev/null +++ b/csunplugged/topics/content/en/sorting-networks/sorting-networks.yaml @@ -0,0 +1,4 @@ +unit-plans: + - unit-plan/unit-plan.yaml + +icon: img/topics/sorting-network-kids.png diff --git a/csunplugged/topics/content/en/sorting-networks/unit-plan/lessons/investigating-variations-using-the-sorting-network-ct-links.md b/csunplugged/topics/content/en/sorting-networks/unit-plan/lessons/investigating-variations-using-the-sorting-network-ct-links.md new file mode 100644 index 000000000..77b01cd1f --- /dev/null +++ b/csunplugged/topics/content/en/sorting-networks/unit-plan/lessons/investigating-variations-using-the-sorting-network-ct-links.md @@ -0,0 +1,71 @@ +Many of the connections are covered in the unit plan and lesson 1. Here are some additional connections: + +{panel type="ct-algorithm" title="Algorithmic thinking"} + +When comparing words for alphabetical order, the algorithm involves comparing the two words letter by letter, and basing the decision of the first pair of letters that differ. + +#### What to look for: + +Were students able to systematically compare words? +Can they articulate the algorithm, particularly when comparing words with a long prefix in common (such as "computer" and "computing")? + +{panel end} + +{panel type="ct-abstraction" title="Abstraction"} + +Sorting Networks can work for any type of data that can be compared. +This means the we do not need to know what the data is, we just need to know how to compare it and order it. + +#### What to look for + +Did students recognise that different types of data could be compared with the same Sorting Network and same process? Can they come up with new types of data to sort? + +{panel end} + +{panel type="ct-decomposition" title="Decomposition"} + +Instead of simply comparing two values at each node, when students compare words in this activity they must break this down into smaller steps. +When comparing words to see which comes first, the process involves a letter by letter comparison until they find two letters that differ. + +#### What to look for + +Were students able to break down the task of comparing words into single letter comparisons? + +{panel end} + +{panel type="ct-pattern" title="Generalising and patterns"} + +In this lesson the comparisons we moved from comparing numbers to the idea of comparing information in general. +This meant we were able to compare other things like letters, words, and musical notes. + +#### What to look for + +Did students recognise that comparing other types of information, such as words and notes, would sort them into a relevant order? +For example according to alphabetical order and musical pitch respectively? + +{panel end} + +{panel type="ct-evaluation" title="Evaluation"} + +We evaluated if the Sorting Network would work backwards. +If we find one example that fails, then that establishes that it can't be used in that way. + +#### What to look for + +Did students recognise that the one example of a Sorting Network not working backwards was enough to show that it isn't a valid Sorting Network? + +{panel end} + +{panel type="ct-logic" title="Logic"} + +As described in the unit plan, if the data being sorted have a transitive relation then the Sorting Network will be able to sort them, and each of the types of data we used in this lesson has this transitive relation. + +#### What to look for + +Are students able to recognise that each of the sets of items compared in this exercise have a transitive relation? Could they identify the most logical comparison to use (such as alphabetical order and the pitch of notes)? + +Can they think of any types of data that don’t have a transitive relation, and that we can’t sort with the Sorting Network? + +One answer could be putting food into order of tastiness - If you like pies more than spaghetti, and your friend likes lasagne more than spaghetti, that doesn’t necessarily mean you like lasagne more than pies, and your friend might not like spaghetti more than pies! + +{panel end} diff --git a/csunplugged/topics/content/en/sorting-networks/unit-plan/lessons/investigating-variations-using-the-sorting-network.md b/csunplugged/topics/content/en/sorting-networks/unit-plan/lessons/investigating-variations-using-the-sorting-network.md new file mode 100644 index 000000000..5e2f3aec3 --- /dev/null +++ b/csunplugged/topics/content/en/sorting-networks/unit-plan/lessons/investigating-variations-using-the-sorting-network.md @@ -0,0 +1,168 @@ +# Investigating variations using the Sorting Network + +## Preparatory knowledge + +Students should have completed lesson 1 to introduce Sorting Networks. + +## Key questions + +- In the Sorting Network, what do we think will happen if the smaller card goes right instead of left at each box and vice versa? + (Students should be able to reason that the values will come out in reverse sorted order.) + +- Will it work if we try to use the Sorting Network backwards, starting with the mixed-up numbers at the output end, and working backwards? + (Students may have different views on this; it appears to work most of the time, but in this lesson we will find an example that doesn't.) + + +## Lesson starter + +Show the students the Sorting Network again (if the network needs redrawing then students often enjoy doing this, and drawing it accurately from the diagram is a useful exercise). +Tell them that they will be trying it with some variations this time. + +Several variations are shown below, and you can choose the ones that suit the students, or you may come up with other items that could be sorted. +The key is that the comparisons obey the transitive rule: that if a is smaller than b, and b is smaller than c, then a is smaller than c. +Sorting by student height or other personal attributes can be problematic - not only might it be a sensitive issue, but comparing two students to find the highest might not give a consistent result if they are a similar height. + +{panel type="math" title="Mathematical links"} + +Predicting outcomes: by understanding how the Sorting Network works students will be investigating different ways of using the Sorting Network and exploring how the outputs are affected by these changes. + +{panel end} + +## Variations with numbers + +This part of the lesson explores changing the way the numbers are used. + +### Variation 1: Identical value + +{image file-path="img/topics/sorting-network-equal-3.png"} + +In this variation, students try the Sorting Network with a set of cards where some cards have an identical value, such as 1, 2, 3, 3, 4, 5. +They will probably ask what to do when comparing the identical cards - ask them what they think, and they are likely to realise that it won't make any difference (if 3 and 3 meet, then it won't matter which one goes left and which goes right!) +Ask them to predict will happen at the end of the network (they may realise that the identical values will end up adjacent). + +Now run the numbers through the network to check. +Here's a brief reminder of the Sorting Network instructions; full details are in lesson 1. + +1. Six students start in the input circles, each holding a card with one of the numbers on it. + +2. They all step forward at the same time, and when they meet someone in a box, they compare their cards. + +3. The person with the smaller card follows the line out to the left, and and the larger card to the right (this is reversed in the second variation for this lesson). + +4. This continues until all the students reach the output circles, at which point they should be in sorted order. + +### Variation 2: Larger to the left + +This time, the person with the larger number goes to the left instead of the right and follows the line to the next square, while the person with the lower number goes to the right instead of the left and follows the line to the next square. + +Ask the students to predict what will happen (they should be able to work out that the values will come out in reverse sorted order i.e. from largest to smallest instead of smallest to largest). + +Have them try it out with some numbers to check it. + +{panel type="teaching" title=""Teaching observations"} + +By reversing the left/right decision, the final result will be in the reverse order to how it would have been in lesson 1. + +{panel end} + +### Variation 3: Letters of the alphabet + +{image file-path="img/topics/sorting-network-variation-alphabet.png"} + +Give the students cards with letters on them. +Ask how we could compare these (students should observe that they could be in alphabetical order). +Have them test this by sorting the cards. + +### Variation 4: Words made of letters in alphabetical order + +As an interesting variation of sorting letters, there are some English words that have the letters in alphabetical order, such as BIOPSY. +If you give the students the letters out of order (such as P, O, I, B, Y, and S) and have them sort them in the Sorting Network, it will form the world BIOPSY at the end. +There are few common words with this property; other examples include ALMOST and ABHORS. +Have the students try the Sorting Network with some of these words (note that you will need to read the sorted letters from the direction of the starting position to see the word in the correct order). + +There is also a number of words that have the letters in reverse alphabetical order, such as SPONGE and ZONKED (these can be sorted using the "larger to the left" variation, or can be read from the far side of the Sorting Network). +Some words with this property have double letters in them, such as BELLOW; these will sort correctly, since the order of the double letters is immaterial. + +{image file-path="img/topics/sorting-network-toffees-cellos-sponge.png"} + +{panel type="general" title="List of words with letters in alphabetical order"} + +Here is a longer list of 6-letter words that can be used for this exercise. +They are all from a dictionary, although some are rather obscure! + +AFFLUX, AGLOOS, ALMOST, BEGILT, BEGINS, BEGIRT, BEKNOT, BELLOW, BIJOUX, BILLOW, BIOPSY, BLOOPS, BLOTTY, CELLOS, CHIKOR, CHILLS, CHILLY, CHIMPS, CHINOS, CHINTZ, CHIPPY, CHIRRS, CHITTY, CHIVVY, CHOOSY, CHOPPY, CLOOPS, CLOTTY, DEFFLY, DEHORT, DEKKOS, DIKKOP, DIMPSY, EFFLUX, EFFORT, ELLOPS, FILLOS, FLOORS, FLOOSY, FLOPPY, FLOSSY, GHOSTY, GIMMOR, GLOOPS, GLOOPY, GLOPPY, GLOSSY, HILLOS, KNOTTY, JIGGED, LIGGED, MIFFED, NIFFED, PIGGED, POLKED, POLLED, POLLEE, POMMEE, POMMIE, PONGED, PONGEE, PONIED, PONKED, POOHED, POOLED, RIFFED, RIGGED, ROLFED, ROLLED, RONNIE, ROOFED, ROOKED, ROOKIE, ROOMED, ROOMIE, SOGGED, SOOGEE, SOOKED, SOOLED, SOOMED, SPLIFF, SPOKED, SPONGE, TIFFED, TIGGED, TOFFEE, TOGGED, TOLLED, TOLLIE, TOMMED, TONGED, TONKED, TOOLED, TOOMED, TOONIE, TROKED, UNFEED, VOMICA, VUMMED, WIGGED, WOLFED, WONNED, WOOFED, WOOLED, WOOLIE, WOONED, WULLED, WURLIE, YOKKED, YOLKED, YONNIE, YTTRIA, YTTRIC, YUKKED, YUPPIE, YWROKE, ZIGGED, ZONKED, ZOOMED, ZOONED, ZOONIC. + +{panel end} + +### Variation 5: Sorting words in dictionary order + +{image file-path="img/topics/sorting-network-variation-words.png"} + +Give the students cards with dictionary words on them, and ask how these might be compared. +Students should observe that they could be placed in dictionary order. +A variation is to give them books and have them sort them in order of the authors' names. + +{image file-path="img/topics/sorting-network-crochet-v-crocodile.png"} + +Comparing two words or names is challenging; they will need to know to compare each character until two differ (e.g. for "crochet" and "crocodile", the "croc" prefix is the same, so it is the "h" and "o" that determine their order; this process is an algorithm in itself!) + +{image file-path="img/topics/sorting-network-variation-words-2.png"} + +The words being compared could also be used to reinforce spelling or meaning; for example, the words above are the colours in Te Reo Māori, so the student with the word "kowhai" would be reinforcing that it means the colour yellow. +The use of macrons and other diacritical marks also gives the opportunity to explore the order that is used in the such languages for those letters. + +### Variation 6: Music notation + +{image file-path="img/topics/sorting-network-variation-music.png"} + +Students can compare the pitch of music notation, with higher notes going to the right. +If all the cards have the same clef (such as the treble clef here) then it reinforces that the height on the stave corresponds to the pitch. +Advanced music students can do the comparisons with different clefs (bass, alto and/or tenor) to exercise note reading. + +### Variation 7: Music pitch - aural + +{image file-path="img/topics/sorting-network-variation-aural.png"} + +In this variation, students compare the pitch of simple instruments that they are carrying. +The bells shown above are ideal because they are all the same size, and force students to compare them by listening. +This variation can be challenging because students need to learn what high and low notes are; it can help to have a teacher or music student help with any comparisons that the students aren't sure about, and it may pay to start with notes that aren't close in pitch. + +Choosing the 6 notes from a pentatonic scale (e.g. C, D, E, G, A, C) happens to work well, as the sound of all 6 being compared at the same time is a little more pleasant! + +## Using the network backwards + +This is an experiment that addresses a question that students may have asked: does the Sorting Network correctly sort the values if we start at the other end? + +Have students try this with some simple values (such as the numbers 1 to 6). +Chances are that it will work for many starting orders of the values. +However, encourage them to keep trying until they find an initial order for which it doesn't work. +This will require considerable reasoning to achieve. + +If they struggle to find an example, you could give the one below, and then challenge them to find a different one that doesn't come out sorted. + + +{panel type="teaching" title="Teaching observations"} + +The Sorting Network is designed to work consistently one way, rather than working both ways. +For example, the first image below shows an input that happens to come out sorted when going through the network backwards, while the second one doesn't. +If it fails on just one input (the second one) then we can't rely on it, even though it sometimes works. +In the other direction, it will always sort correctly. + +{panel end} + +{image file-path="img/topics/sorting-network-sorted.png"} + +{image file-path="img/topics/sorting-network-sorted.png"} + +## Applying what we have just learnt + +This kind of algorithm needs to run on special hardware to take advantage of doing multiple comparisons at the same time. +It is only used for specialist applications at present, for example it is sometimes done on the graphics processor (GPU) of a computer, because these processors are good at doing parallel computation. +Sorting Networks were invented long before powerful GPUs came along; this is an exciting thing about Computer Science - some of the our discoveries are ahead of the hardware that is available, so we're ready to make use of the hardware when it does become commonly available! +Note that this is not a conventional sorting algorithm, as the sorting that is done on a conventional system can make only one comparison at a time; conventional sorting algorithms are explored in another lesson. + +## Lesson reflection + +What did you notice happen with each variation of using the Sorting Network? + +Was it what you had expected? diff --git a/csunplugged/topics/content/en/sorting-networks/unit-plan/lessons/lessons.yaml b/csunplugged/topics/content/en/sorting-networks/unit-plan/lessons/lessons.yaml new file mode 100644 index 000000000..40015f7ae --- /dev/null +++ b/csunplugged/topics/content/en/sorting-networks/unit-plan/lessons/lessons.yaml @@ -0,0 +1,35 @@ +reinforcing-numeracy-through-a-sorting-network: + duration: 45 + learning-outcomes: + - sorting-explain-parallel-algorithm + - sorting-use-selection-sort + - sorting-identify-number-range + - sorting-organise-objects-size + computational-thinking-links: reinforcing-numeracy-through-a-sorting-network-ct-links.md + generated-resources: + sorting-network: + description: Use blank copy as template for creating Sorting Network. + sorting-network-cards: + description: Cards for students to use with the Sorting Network. + classroom-resources: + - 6 cards (or other items such as post-it notes) with numbers on them + - Stopwatch (e.g. on smartphone) to time races between groups. + +investigating-variations-using-the-sorting-network: + duration: 30 + learning-outcomes: + - sorting-recognise-sorting-method + - sorting-demonstrate-alphabet-knowledge + - sorting-comparing-musical-pitch + - sorting-explain-parallel-algorithm + - sorting-use-selection-sort + computational-thinking-links: investigating-variations-using-the-sorting-network-ct-links.md + generated-resources: + sorting-network: + description: Use blank copy as template for creating Sorting Network. + sorting-network-cards: + description: Cards for students to use with the Sorting Network. + classroom-resources: + - A variety of sets of 6 number cards that each include one or more repeated numbers, or + - Books with author names, or + - Sets of instruments with different pitches diff --git a/csunplugged/topics/content/en/sorting-networks/unit-plan/lessons/reinforcing-numeracy-through-a-sorting-network-ct-links.md b/csunplugged/topics/content/en/sorting-networks/unit-plan/lessons/reinforcing-numeracy-through-a-sorting-network-ct-links.md new file mode 100644 index 000000000..71c0fb532 --- /dev/null +++ b/csunplugged/topics/content/en/sorting-networks/unit-plan/lessons/reinforcing-numeracy-through-a-sorting-network-ct-links.md @@ -0,0 +1,92 @@ +{panel type="ct-algorithm" title="Algorithmic thinking"} + +We used an algorithm in this lesson to sort the numbers into order using a +parallel processor (normally this processor would be implemented in hardware, +but our chalk network is still actually one! +It’s powered by people instead of electricity). + +#### Examples of what you could look for: + +- Do students understand how each node functions (taking in two values and + swapping them if they are in the wrong order)? + Are they able to explain to other students how to use the network + correctly? + +- Do the students see that no matter what numbers or data we put into the + network we will always get a solution if we follow the algorithm correctly? + +{panel end} + +{panel type="ct-abstraction" title="Abstraction"} + +The Sorting Network used in these activities is itself an abstract +representation of how Sorting Networks are implemented in hardware and +software. +It represents the core functionality of a Sorting Network, whilst hiding all +the nitty gritty details of how the hardware and circuitry works. + +#### Examples of what you could look for: + +- Can students make the connection between the lines and nodes on this graph + and the way computers can process information by making comparisons? +- Can students understand that this representation can be used to model how a + real parallel processing computer would work? + +{panel end} + +{panel type="ct-decomposition" title="Decomposition"} + +The whole process of sorting in this activity is decomposed into a very simple +operation: comparing two values. +This operation alone is very simplistic, but when it is performed many many +times it can be used to build up a solution to a much larger task. + +#### Examples of what you could look for: + +- Can students see how to design a Sorting Network to sort just 2 values? + (It would just be a single comparator node). + +{panel end} + +{panel type="ct-pattern" title="Generalising and patterns"} + +In this lesson students only worked with one type of information, numbers, so +there wasn’t much use of generalisation. +It is more prominent in the next lesson. + +{panel end} + +{panel type="ct-evaluation" title="Evaluation"} + +For this Sorting Network there can be up to three comparisons happening at +once, and the length of the network determines how long it would take to +complete all these comparisons. +Although 12 comparisons need to be made when going through the network, the +network can be completed in the time it takes to an individual node to make 5 +comparisons. + +#### Examples of what you could look for: + +- Can students identify the longest path that any number would have to go + through to get to the end? + (The middle two numbers need to make 5 comparisons). +- Can students explain that if every comparison were to take, say, 2 seconds, + then the sorting would be finished in 5x2 seconds, and not 12x2 seconds? + +{panel end} + +{panel type="ct-logic" title="Logic"} + +The smallest value will always take the left path at any comparison, and from +every starting point the path that always takes the left branch will lead to +that node, the smallest value will therefore always end up in the left-most +position at the end. + +#### Examples of what you could look for: + +- Can students explain where the smallest value will end up regardless of + what the other values are? +- Do students understand the function of each node? + Do they avoid simply going to the final node without doing the comparisons? + +{panel end} diff --git a/csunplugged/topics/content/en/sorting-networks/unit-plan/lessons/reinforcing-numeracy-through-a-sorting-network.md b/csunplugged/topics/content/en/sorting-networks/unit-plan/lessons/reinforcing-numeracy-through-a-sorting-network.md new file mode 100644 index 000000000..cf1e7b34d --- /dev/null +++ b/csunplugged/topics/content/en/sorting-networks/unit-plan/lessons/reinforcing-numeracy-through-a-sorting-network.md @@ -0,0 +1,187 @@ +# Reinforcing numeracy through a Sorting Network + +## Key questions + +What are examples of tasks get finished sooner if more people help with them? +What are examples of tasks that don't get finished sooner if more people help +with them? + +### Potential answers could include: + +{image file-path="img/topics/sorting-network-office-note-text-en.png" alt="A group of students all carry a note to the office."} + +Tasks such as tidying the classroom, picking up rubbish, or reshelving library +books may come up as ones that benefit from multiple helpers. +Things that don't go faster might include delivering a note to the office (10 +people delivering the note won't get it there 10 times faster), or washing +dishes if there is only one sink (two people are faster than one, but more +people probably can't speed it up). + +## Lesson starter + +Use the Sorting Network template to draw a 6 person Sorting Network on a paved +surface outside using chalk (other alternatives include using masking/painters +tape on a carpet or wooden floor, tape on a tarpaulin, or line marking paint on +grass). +Note that the Sorting Network needn't use different colours or line +thicknesses, but if suitable chalk or tape is available, this can help students +remember which way to go. +It should be large enough that two students can comfortably stand in the +rectangles; the more spread out it is, the more effective the exercise is. +In a very confined situation, it could be done on a desk top using game +counters instead of students moving around, but this is much less engaging. + +Show the students the Sorting Network drawn on the ground, and tell them "This +chalk computer can do some things very fast, let’s investigate what it does." + +{panel type="math" title="Mathematical links"} + +Supports students understanding of ordering any range of numbers, from ordering +single digit numbers to fractions and decimals, or numbers in their millions. + +{panel end} + +{panel type="exemplars" title="Exemplars"} + +Here are examples of the kinds of numbers you could use to reinforce number +ordering (two of each set of six cards are shown). +It's good to start with the single digit numbers to get students used to the +system, and then provide more difficult numbers appropriate to the students' +ability level. + +{image file-path="img/topics/sorting-network-example-cards-1.jpg" alt="Two pieces of paper with single digit numbers printed on them."} + +{image file-path="img/topics/sorting-network-example-cards-2.jpg" alt="Two pieces of paper with seven digit numbers printed on them."} + +{image file-path="img/topics/sorting-network-example-cards-3.jpg" alt="Two pieces of paper with fractions printed on them."} + +{panel end} + +## Lesson activities + +{image file-path="img/topics/sorting-network-kids.png" alt="A group of children sort items on a Sorting Network drawn on concrete."} + +1. Organise students into groups of six. + Only one team will use the network at a time. +2. The current team should stand on the circles at the "input" end of + the Sorting Network. +3. Give each of the six students a card to hold (initially use the set of + cards containing the numbers from 1 to 6; the cards should be given to + the students out of order). + These cards are the inputs into this cool chalk computer (this is a + special kind of computer that can process several operations at the same + time). +4. Get the first two students to follow the lines from their circles until + they meet at a box (the others should pay attention). +5. When the two have entered the box, they should say “Hello” to each other + (this is to make sure that they stop and both engage in this step), and + then compare cards to decide who has the lower number and who has the + higher number. +6. The student with the lower number should follow the line out to the left + and go to the next box, while the person with the higher number follows the + line leaving to the right to go to the next square. +7. Now get the next pair of students to do the same, meeting at a box and + leaving it with the smaller to the left and the larger to the right. +8. You can now get the remaining pair of students to do this (remind them to + say hello when they meet). +9. Once they have the idea, tell them to repeat this process until they get to + the end of the network. + If someone gets left behind, have the students go back to the beginning; + they will need to pay attention when they meet at a square, and ensure that + both people who have met know the outcome. +10. When they have all reached the circles at the other end of the network have + them turn and face the starting circles and read what’s on their card, from + left to right. + They should be in the correct order from smallest to largest; if not, they + may need to try again and work more carefully. +11. When each group has been through the Sorting Network, introduce a Sorting + Network race to see which group can successfully complete the task in the + shortest amount of time (either with two Sorting Networks racing teams at + the same time, or one network with the times measured using a stopwatch). + +{panel type="teaching" title="Teaching observations"} + +If it didn’t work it may be because a pair incorrectly went to the wrong square +or a person raced ahead of everyone else. +Have the group repeat the task and check each comparison. +If it doesn’t work a second time, bring in student “testers” to confirm that +each square has made the right decision which person is to go to the left and +the right. +Encouraging them to say "hello" when they meet at a square helps to avoid +someone heading off before they have made a decision on the values. + +{image file-path="img/topics/sorting-network-too-far-kid.png" alt="A child walks too far in the sorting network activity, failing the activity for everyone."} + +If a student races to the end ahead of everyone else because they already know +where their number will go once the numbers are sorted (this happens quite +often!) then some students are going to be left stuck inside the network +because they don’t have someone to compare numbers with. +This is a good opportunity to remind students that computers need to follow the +instructions they are given precisely to make sure they achieve the correct +result; it also reinforces the need for teamwork! + +{panel end} + +## Applying what we have just learnt + +This technique with parallel instructions can't run directly in the kind of +computer system that students are likely to be learning about, as simpler +systems can only compare one pair of values at a time, while this one is +comparing up to three pairs of values at the same time. +But although this algorithm hasn’t been written to work on a conventional +system, there is still an algorithm to be observed, a parallel algorithm, and +this can be implemented with specialised hardware and software. +The challenge with creating parallel algorithms is to have as many things +happening at the same time as possible, so that we can get things done faster. +However, it's not always easy to break a problem up so that separate parts can +happen at the same time, as often each comparison depends on the results of +another. +The diagram that we used above happens to be the shortest one we can design for +sorting 6 values. + +How do we know this Sorting Network is reliable and works every time? + +The outcome we want to achieve is that the numbers come out in the correct +order with the smallest number being in the left-most box and the second +smallest number finishing next to it, right through to the largest number being +in the right-most box. +If we want to make sure it works for all possible inputs, then we would need to +try it for every order that the values might start in - it turns out that there +are 720 different orderings that 6 items can start in, so that's a lot of cases +to test. +For sorting more than 6 items, there are way too many different orderings to +try out, so we must make a mathematical proof of why it works. +Here are some elements of such a proof: + +Let’s disregard the numbers for now and look at the Sorting Network from the +point of view of following the paths. +If the smallest number was in node 1, what path would it take and does it end +up in the leftmost node at the end? + +{image file-path="img/topics/sorting-network-node-1.png" alt="An image showing the network with the path the smallest number would take through the network if the smallest number started in node 1."} + +Now repeat this by asking if the smallest number was in node 2, what path would +it take and does it still end up in the leftmost node at the end? + +{image file-path="img/topics/sorting-network-node-2.png" alt="An image showing the network with the path the smallest number would take through the network if the smallest number started in node 2."} + +Repeat this until you’ve tested all 6 nodes. +If the smallest number ends up in the leftmost node regardless of where it +starts, that's part-way to being sure that the structure always works. + +You can repeat this with the largest number - no matter where it starts, it +will always end up in the right-most node. + +Doing this for the other four values (e.g. the second to largest) isn't quite +as simple, but computer scientists are able to prove that they will also always +end up in their correct position. + +## Lesson reflection + +- Was there ever a situation where the cards weren’t sorted in the right + order? + What had happened for that to occur? + How was it corrected? +- Can you trace the pathways for the lowest number if it was placed in any + position? + What about the largest number? diff --git a/csunplugged/topics/content/en/sorting-networks/unit-plan/unit-plan-ct-links.md b/csunplugged/topics/content/en/sorting-networks/unit-plan/unit-plan-ct-links.md new file mode 100644 index 000000000..3b41255b2 --- /dev/null +++ b/csunplugged/topics/content/en/sorting-networks/unit-plan/unit-plan-ct-links.md @@ -0,0 +1,125 @@ +{panel type="ct-algorithm" title="Algorithmic thinking"} + +In these lessons students will be sorting a variety of things into order, but +the underlying algorithm for performing these tasks will remain the same. +It is an algorithm because it is a step-by-step process that will always give +the right solution, as long as it is followed exactly. +In this case it is a special class of algorithm called a "parallel algorithm". +Students will need to follow this algorithm precisely to get to the correct +solution (this is particularly clear when students try to ‘cheat’ by dashing +straight to the end of the network, and then realise this means other students +are now stuck in the middle! +It’s a great learning opportunity when someone does this). + +{panel end} + +{panel type="ct-abstraction" title="Abstraction"} + +The Sorting Network we use in these activities is a simple representation of +something much more complex: how Sorting Networks are implemented using specific +hardware and software on some computers to perform parallel processing. +The lines, circles, and squares we will use in our Sorting Networks hide the +complicated details of the hardware and software. + +Another detail we can ignore when we are using a Sorting Network is what the +data we are sorting actually is, or represents. +It doesn’t matter if we are sorting numbers into order, or words, or musical +notes, we will still follow the same process each time. +The one thing about the data that does matter however is that we can compare +each item and that they have a precise way of being ordered (e.g. alphabetical +order). +This is described further in the section on logic. + +The overall idea of a Sorting Network is actually an abstract concept as well, +this is explained under the generalisation heading. + +{panel end} + +{panel type="ct-decomposition" title="Decomposition"} + +{image file-path="img/topics/sorting-network-comparing-apples.png" alt="A person compares a large apple and a small apple."} + +In order to create an algorithm that can solve computational problems +effectively using parallel processors, we must first be able decompose the task +into very small and basic operations that, when repeated many times, can build +up a solution to the problem. +This operation is what will be performed by each processor in the network. +For the Sorting Network in these lessons this basic operation is the comparison +of two values that we perform at each node. +These operations need to be so basic that nodes can perform them simultaneously +and independently. +Parallel algorithms work best for tasks that need to do repetitive, and +independent, calculations with large amounts of data. + +Decomposition is one of the most important steps in creating parallel processing +algorithms! + +{panel end} + +{panel type="ct-pattern" title="Generalising and patterns"} + +There are many links between this section and the abstraction section above, see +if you can spot them! + +The Sorting Networks we will look at are each constructed to take in a specific +number of inputs, and that number only. +We can’t use a Sorting Network that sorts 6 numbers to sort 10 numbers instead. +However the generalised idea of a Sorting Network can be applied to different +problems. +The generalised concept of a Sorting Network is simply a comparator network +(comparator just means it makes comparisons, like how we compare numbers in each +of the circles in the network) that takes in a number of inputs, and sorts them +into order. +This general idea of a Sorting Network can then be applied to solving many +different problems, by creating a Sorting Network for the specific number of +inputs needed for the problem and placing its comparison nodes in a specific +pattern. + +There are patterns in the layout of Sorting Networks as well; recognising these +helps us design larger networks. +For example, the (optimal) two-way, four-way and six-way Sorting Networks follow +a similar pattern in their layout. +A simple pattern for generating Sorting Networks is explored at the end of +lesson 3 for ages 11-14 (but this can be used with any age group if the students +are interested!). + +There is also a common pattern that students can observe between all the +different types of information we sort with the Sorting Network, which is that +they can be compared and ordered in a precise way. +This is described in the logic section. + +{panel end} + +{panel type="ct-evaluation" title="Evaluation"} + +Parallel systems need to be evaluated for correctness: do they always sort +values correctly? +They also need to be evaluated for efficiency: how much time does this network +arrangement take to sort values, and is there a faster arrangement we could use? +Could this problem be solved easier by a non-parallel system? + +{panel end} + +{panel type="ct-logic" title="Logic"} + +A very important rule for the data that Sorting Networks can process is the data +must have something called a transitive relation. +The transitive relation means: if a is less than b, and b is less than c, then a +is less than c. +For example, numbers have a transitive relation: the number 5 is less than 10, +and 10 is less than 15, which means that 5 must also be less than 15. +Data must have this relation for a Sorting Network to be able to sort it. +If items don’t have this relation then there is no logical way for us to order +it! + +We will also see that Sorting Networks can't be evaluated by trying every +possible input (well they could be, but it could take hours, days, or even +hundreds of years for big networks!), unless a very small amount of data is +being sorted. +So instead, we must apply logic and reason to prove why it will always sort the +data correctly. +In these lessons we don't get into the advanced proofs that the whole network +will work, but students can apply their logical thinking skills to prove that +the smallest and largest values will always end up in the correct place. + +{panel end} diff --git a/csunplugged/topics/content/en/sorting-networks/unit-plan/unit-plan.md b/csunplugged/topics/content/en/sorting-networks/unit-plan/unit-plan.md new file mode 100644 index 000000000..92fdf2fe7 --- /dev/null +++ b/csunplugged/topics/content/en/sorting-networks/unit-plan/unit-plan.md @@ -0,0 +1,170 @@ +# Sorting networks unit plan + +{panel type="teaching" title="See teaching this in action!"} + +A demonstration of sorting networks being taught is available here: + +{video url="https://www.youtube.com/embed/M-z5pDjqtZk"} + +Some other videos showing different situations using Sorting Networks: + +- [Video 1](https://www.youtube.com/watch?v=LOxfdsBBjKI) +- [Video 2](https://www.youtube.com/watch?v=30WcPnvfiKE) + +{panel end} + +## What’s it all about? + +As we use computers more and more, and the amount of data we use increases, we +want them to process information as quickly as possible. +One way to increase the speed of a computer is to write programs that use fewer +computational steps (as shown in the lessons on sorting and searching +algorithms). +Another way to solve problems faster is to have several computers work on +different parts of the same task at the same time, which is what this unit +explores. +Unfortunately it's not always that simple to just split the work among separate +processors! + +{image file-path="img/topics/sorting-network-many-computers-vs-one.png" alt="An image showing a group of people working on their computers working, compared to one person at their computer."} + +Sorting Networks are used to sort values into ascending order by comparing pairs +of values; unlike a conventional sorting algorithm, a Sorting Network can have +more than one comparison happening at the same time. +For example, in the six-number Sorting Network that we use a lot in this unit, +a total of 12 comparisons are used to sort the numbers, but up to three +comparisons can be performed simultaneously. +This means that the time required will the same as what one computer by itself +would take to make only 5 comparison steps. +It's a bit like the situation where you might need to type in 4 pages of +writing; if you have 4 people typing at the same time on 4 computers, then you +can probably get the typing done 4 times faster than if one person did all the +work. + +A parallel Sorting Network enables us to explore how much faster we can sort +values into order if we can make simultaneous comparisons. +The main six-way parallel network used in these lessons sorts a list of values +more than twice as quickly as a system that can only perform one comparison at +a time. + +{image file-path="img/topics/sorting-network-digging-hole-text-en.png" alt="One person is digging a hole and the other person states they can't start digging until the other person is done."} + +Not all tasks can be completed faster by using parallel computation however. +As an analogy, imagine one person digging a ditch ten metres long. +If ten people each dug one metre of the ditch at the same time the task would +be completed much faster. +However, the same strategy could not be applied to a ditch ten metres deep, the +second metre is not accessible until the first metre has been dug. + +{image file-path="img/topics/sorting-network-confused-people.png" alt="A group of people are confused in front of computers as they try to coordinate a simple job across many people."} + +And for typing our four page document, if you have 400 people helping, you'll +probably spend so much time coordinating all the work that it might not be very +fast at all! +Computer Scientists are still actively trying to find the best ways to break +problems up so that they can be solved by computers working in parallel, finding +out how much, and which parts, of the computation can be done at the same time, +and which parts have to be done one after another. + +In these lessons we use a fun team activity to demonstrate an approach to +parallel sorting. +It can be done on paper, but we like to get students to do it on a large scale, +running from node to node in the network. + +As an aside, although this is called a "network", it is only one of many +different types of networks that we encounter in Computer Science. +A common kind of “network” is a communication network, such as the +telecommunication networks that mobile phones use, and of course the Internet! +There are also networks for representing things like road maps and airline +routes. +It’s important to recognise that the Sorting Network in this activity is **not** +one of these types of networks, it is called a comparator network, because it’s +a network where each node compares two values, rather than linking different +devices (such as phones and computers) together. + +## Digital Technologies | Algorithms + +{image file-path="img/topics/sorting-network-too-far-kid.png" alt="A child walks too far in the sorting network activity, failing the activity for everyone."} + +To use the Sorting Network students need to follow a simple algorithm and should +recognise that if they do not follow this algorithm precisely, the way a +computer would, then they will probably not get to a correct result, or may not +get a result at all! +Students will be working collaboratively to ensure that each part of the +algorithm is coordinated, because if one person moves too far ahead, without +stopping at the nodes they are meant to, it causes the process to fail for +everyone. +There are also many algorithms that are used to construct extremely efficient +Sorting Networks of different sizes, and Computer Scientists study these to try +and create even better ones. +These can be very complex however, so when students construct their own networks +they will be doing this in a simplified way. + +## Vocabulary explained + +- **Processor/CPU:** A device that can run computer programs. +- **Parallel processing:** Using multiple processors to work on different + parts of a problem at the same time. +- **Serial processing:** Running a program on a single processor, so all the + instructions are executed one after the other. +- **Network:** A series of connected nodes such as a computer network, a road + map, or a comparator network. +- **Computational step:** A basic operation that is part of an algorithm. +- **GPU/Graphics Processing Unit:** A specialised processor in a computer that + can do simple operations for the many pixels in an image in parallel. + These are often used for other computations because of their ability to do + parallel processing. + +{panel type="math" title="Mathematical links"} + +This activity strongly supports learning about the concept of before and after +(ordering) for numbers, including determining the relationship between two +numbers (greater than, less than). + +{panel end} + +## Real world implications + +{image file-path="img/topics/sorting-network-tortoises-vs-rabbit.png" alt="A group of turtles build a wall quicker than one rabbit."} + +Often it's cheaper, and faster, to have a number of slow processors work on a +computational problem, rather than one very fast one. +Companies that have massive cloud servers often find it more economical to have +many slower, cheaper devices rather than fewer expensive ones. +Of course, this requires you to be able to split up a computational task over +several processors. +For some computational problems that's very easy to do, and for others it's +impossible. +The task we will be looking at here is between these extremes. + +Having such a small operation (comparing two values) split over multiple +devices means that this kind of algorithm needs to run on special hardware. +It is only used for specialist applications at present, but, for example, it is +sometimes done on the graphics processor (GPU) of a computer, because these +processors are good at doing parallel computation of simple tasks. + +{image file-path="img/topics/sorting-network-ancient-sorting-network-text-en.png" alt="A GPU finds a cave painting of an ancient sorting network."} + +Sorting Networks were invented long before powerful GPUs came along; this is an +exciting thing about Computer Science - some of the our discoveries are ahead +of the hardware that is available, so we're ready for the hardware if it does +become commonly available! +Note that the approach explored in these lessons is **not** a conventional +sorting algorithm, as the sorting that is done on a conventional system can make +only one comparison at a time; conventional sorting algorithms are explored in +another lesson. +The main goal of these lessons is to help students explore the tradeoffs between +spreading work over several processors instead of using one processor. + +One approach to parallel computation that is currently popular is called +"MapReduce", which is widely used in Cloud Computing systems where large amounts +of computing are spread over a large number of processors. + +## Reflection questions + +- What was most surprising about the learning that happened from the teaching + of this unit? +- Who were the students that were very systematic when working through the + activities? +- Who were the students who were very detailed in their activities? +- What would I change in my delivery of this unit? diff --git a/csunplugged/topics/content/en/sorting-networks/unit-plan/unit-plan.yaml b/csunplugged/topics/content/en/sorting-networks/unit-plan/unit-plan.yaml new file mode 100644 index 000000000..4d40307a5 --- /dev/null +++ b/csunplugged/topics/content/en/sorting-networks/unit-plan/unit-plan.yaml @@ -0,0 +1,14 @@ +lessons: lessons/lessons.yaml +computational-thinking-links: unit-plan-ct-links.md + +age-groups: + 8-10: + reinforcing-numeracy-through-a-sorting-network: + number: 1 + investigating-variations-using-the-sorting-network: + number: 2 + 11-14: + reinforcing-numeracy-through-a-sorting-network: + number: 1 + investigating-variations-using-the-sorting-network: + number: 2 diff --git a/csunplugged/topics/content/en/structure.yaml b/csunplugged/topics/content/en/structure.yaml new file mode 100644 index 000000000..bee3f2544 --- /dev/null +++ b/csunplugged/topics/content/en/structure.yaml @@ -0,0 +1,11 @@ +topics: + - binary-numbers + - error-detection-and-correction + - sorting-networks + +age-groups: age-groups.yaml +programming-challenges-structure: programming-challenges-structure.yaml +curriculum-areas: curriculum-areas.yaml +learning-outcomes: learning-outcomes.yaml + +glossary-folder: glossary diff --git a/csunplugged/topics/management/__init__.py b/csunplugged/topics/management/__init__.py new file mode 100644 index 000000000..1cb0cd650 --- /dev/null +++ b/csunplugged/topics/management/__init__.py @@ -0,0 +1 @@ +"""Module for the management of the topics application.""" diff --git a/csunplugged/topics/management/commands/_AgeGroupsLoader.py b/csunplugged/topics/management/commands/_AgeGroupsLoader.py new file mode 100644 index 000000000..64ec9efec --- /dev/null +++ b/csunplugged/topics/management/commands/_AgeGroupsLoader.py @@ -0,0 +1,79 @@ +"""Custom loader for loading age group.""" + +import os.path + +from django.db import transaction + +from utils.BaseLoader import BaseLoader +from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError + +from topics.models import AgeGroup + + +class AgeGroupsLoader(BaseLoader): + """Loader for age group content.""" + + def __init__(self, structure_file_path, BASE_PATH): + """Create the loader for loading age groups. + + Args: + structure_file_path: File path to YAML file (str). + BASE_PATH: Base file path (str). + """ + super().__init__(BASE_PATH) + self.structure_file_path = structure_file_path + self.BASE_PATH = os.path.join(self.BASE_PATH, os.path.split(structure_file_path)[0]) + + @transaction.atomic + def load(self): + """Load the content for age groups. + + Raise: + MissingRequiredFieldError: when no object can be found with the + matching attribute. + """ + age_groups_structure = self.load_yaml_file( + os.path.join( + self.BASE_PATH, + self.structure_file_path + ) + ) + + for (age_group_slug, age_group_data) in age_groups_structure.items(): + + if age_group_data is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["min_age", "max_age"], + "Age Range" + ) + + group_min_age = age_group_data.get("min_age", None) + if group_min_age is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["min_age"], + "Age Range" + ) + + group_max_age = age_group_data.get("max_age", None) + if group_max_age is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["max_age"], + "Age Range" + ) + + group_description = age_group_data.get("description", None) + + # Create area objects and save to database + age_group = AgeGroup( + slug=age_group_slug, + ages=(int(group_min_age), int(group_max_age)), + description=group_description, + ) + age_group.save() + + self.log("Added age group: {}".format(age_group.__str__())) + + self.log("All age groups loaded!\n") diff --git a/csunplugged/topics/management/commands/_CurriculumAreasLoader.py b/csunplugged/topics/management/commands/_CurriculumAreasLoader.py new file mode 100644 index 000000000..ebf641176 --- /dev/null +++ b/csunplugged/topics/management/commands/_CurriculumAreasLoader.py @@ -0,0 +1,121 @@ +"""Custom loader for loading curriculum areas.""" + +import os.path + +from django.db import transaction + +from utils.BaseLoader import BaseLoader +from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError + +from topics.models import CurriculumArea + + +class CurriculumAreasLoader(BaseLoader): + """Loader for curriculum area content.""" + + def __init__(self, structure_file_path, BASE_PATH): + """Create the loader for loading curriculum areas. + + Args: + structure_file_path: File path to YAML file (str). + BASE_PATH: Base file path (str). + """ + super().__init__(BASE_PATH) + self.structure_file_path = structure_file_path + self.BASE_PATH = os.path.join(self.BASE_PATH, os.path.split(structure_file_path)[0]) + + @transaction.atomic + def load(self): + """Load the content for curriculum areas. + + Raise: + MissingRequiredFieldError: when no object can be found with the matching + attribute. + """ + curriculum_areas_structure = self.load_yaml_file( + os.path.join( + self.BASE_PATH, + self.structure_file_path + ) + ) + + for (curriculum_area_slug, curriculum_area_data) in curriculum_areas_structure.items(): + + if curriculum_area_data is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["name"], + "Curriculum Area" + ) + + curriculum_area_name = curriculum_area_data.get("name", None) + if curriculum_area_name is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["name"], + "Curriculum Area" + ) + + curriculum_area_colour = curriculum_area_data.get("colour", None) + if curriculum_area_colour is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["colour"], + "Curriculum Area" + ) + + curriculum_area_number = curriculum_area_data.get("number", None) + if curriculum_area_number is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["number"], + "Curriculum Area" + ) + + # Create area objects and save to database + new_area = CurriculumArea( + slug=curriculum_area_slug, + name=curriculum_area_name, + colour=curriculum_area_colour, + number=curriculum_area_number, + ) + new_area.save() + + self.log("Added curriculum area: {}".format(new_area.__str__())) + + # Create children curriculum areas with reference to parent + if "children" in curriculum_area_data: + children_curriculum_areas = curriculum_area_data["children"] + if children_curriculum_areas is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["slug"], + "Child Curriculum Area" + ) + for (child_slug, child_data) in children_curriculum_areas.items(): + if child_data is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["name"], + "Child Curriculum Area" + ) + child_name = child_data.get("name", None) + if child_name is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["name"], + "Child Curriculum Area" + ) + + new_child = CurriculumArea( + slug=child_slug, + name=child_name, + colour=curriculum_area_colour, + number=curriculum_area_number, + parent=new_area, + ) + new_child.save() + + self.log("Added child curriculum area: {}".format(new_child.__str__()), 1) + + self.log("All curriculum areas loaded!\n") diff --git a/csunplugged/topics/management/commands/_CurriculumIntegrationsLoader.py b/csunplugged/topics/management/commands/_CurriculumIntegrationsLoader.py new file mode 100644 index 000000000..0cd90d654 --- /dev/null +++ b/csunplugged/topics/management/commands/_CurriculumIntegrationsLoader.py @@ -0,0 +1,111 @@ +"""Custom loader for loading curriculum integrations.""" + +import os.path + +from utils.BaseLoader import BaseLoader + +from utils.errors.KeyNotFoundError import KeyNotFoundError +from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError + +from topics.models import CurriculumArea, Lesson + + +class CurriculumIntegrationsLoader(BaseLoader): + """Custom loader for loading curriculum integrations.""" + + def __init__(self, structure_file_path, topic, BASE_PATH): + """Create the loader for loading curriculum integrations. + + Args: + structure_file_path: File path for structure YAML file (str). + topic: Object of related topic model (Topic). + BASE_PATH: Base file path (str). + """ + super().__init__(BASE_PATH) + self.structure_file_path = os.path.join(self.BASE_PATH, structure_file_path) + self.BASE_PATH = os.path.join(self.BASE_PATH, os.path.split(structure_file_path)[0]) + self.topic = topic + + def load(self): + """Load the content for curriculum integrations. + + Raise: + KeyNotFoundError: when no object can be found with the matching attribute. + MissingRequiredFieldError: when a value for a required model field cannot be + found in the config file. + """ + structure = self.load_yaml_file(self.structure_file_path) + + for (integration_slug, integration_data) in structure.items(): + + if integration_data is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["number", "curriculum-areas"], + "Curriculum Integration" + ) + + integration_number = integration_data.get("number", None) + integration_curriculum_areas = integration_data.get("curriculum-areas", None) + if None in [integration_number, integration_curriculum_areas]: + raise MissingRequiredFieldError( + self.structure_file_path, + ["number", "curriculum-areas"], + "Curriculum Integration" + ) + + integration_content = self.convert_md_file( + os.path.join( + self.BASE_PATH, + "{}.md".format(integration_slug) + ), + self.structure_file_path + ) + + integration = self.topic.curriculum_integrations.create( + slug=integration_slug, + number=integration_number, + name=integration_content.title, + content=integration_content.html_string, + ) + integration.save() + + # Add curriculum areas + for curriculum_area_slug in integration_curriculum_areas: + try: + curriculum_area = CurriculumArea.objects.get( + slug=curriculum_area_slug + ) + integration.curriculum_areas.add(curriculum_area) + except: + raise KeyNotFoundError( + self.structure_file_path, + curriculum_area_slug, + "Curriculum Areas" + ) + + # Add prerequisite lessons + if "prerequisite-lessons" in integration_data: + prerequisite_lessons = integration_data["prerequisite-lessons"] + if prerequisite_lessons is not None: + for (unit_plan_slug, lessons) in prerequisite_lessons.items(): + if lessons is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["unit-plan"], + "Prerequisite Lesson" + ) + for lesson_slug in lessons: + try: + lesson = Lesson.objects.get( + slug=lesson_slug + ) + integration.prerequisite_lessons.add(lesson) + except: + raise KeyNotFoundError( + self.structure_file_path, + lesson_slug, + "Lessons" + ) + + self.log("Added curriculum integration: {}".format(integration.name), 1) diff --git a/csunplugged/topics/management/commands/_GlossaryTermsLoader.py b/csunplugged/topics/management/commands/_GlossaryTermsLoader.py new file mode 100644 index 000000000..65ad6dcb5 --- /dev/null +++ b/csunplugged/topics/management/commands/_GlossaryTermsLoader.py @@ -0,0 +1,55 @@ +"""Custom loader for loading glossary terms.""" + +import os.path +from os import listdir +from django.db import transaction + +from utils.BaseLoader import BaseLoader +from topics.models import GlossaryTerm + + +class GlossaryTermsLoader(BaseLoader): + """Custom loader for loading glossary terms.""" + + def __init__(self, glossary_folder_path, structure_file_path, BASE_PATH): + """Create the loader for loading glossary terms. + + Args: + glossary_folder_path: Folder path to definition files (str). + structure_file_path: Path to the config file, used for errors (str). + BASE_PATH: Base file path (str). + """ + super().__init__(BASE_PATH) + self.structure_file_path = structure_file_path + self.BASE_PATH = os.path.join(self.BASE_PATH, glossary_folder_path) + self.FILE_EXTENSION = ".md" + + @transaction.atomic + def load(self): + """Load the glossary content into the database.""" + glossary_slugs = set() + for filename in listdir(self.BASE_PATH): + if filename.endswith(self.FILE_EXTENSION): + glossary_slug = filename[:-len(self.FILE_EXTENSION)] + glossary_slugs.add(glossary_slug) + glossary_term = GlossaryTerm( + slug=glossary_slug, + ) + glossary_term.save() + + for glossary_slug in glossary_slugs: + glossary_term = GlossaryTerm.objects.get(slug=glossary_slug) + glossary_file_path = os.path.join( + self.BASE_PATH, + "{}{}".format(glossary_slug, self.FILE_EXTENSION) + ) + glossary_term_content = self.convert_md_file( + glossary_file_path, + self.structure_file_path + ) + glossary_term.term = glossary_term_content.title + glossary_term.definition = glossary_term_content.html_string + glossary_term.save() + self.log("Added glossary term: {}".format(glossary_term.__str__())) + + self.log("All glossary terms loaded!\n") diff --git a/csunplugged/topics/management/commands/_LearningOutcomesLoader.py b/csunplugged/topics/management/commands/_LearningOutcomesLoader.py new file mode 100644 index 000000000..f8975bcd9 --- /dev/null +++ b/csunplugged/topics/management/commands/_LearningOutcomesLoader.py @@ -0,0 +1,77 @@ +"""Custom loader for loading learning outcomes.""" + +import os.path +from django.db import transaction + +from utils.BaseLoader import BaseLoader +from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError +from utils.errors.KeyNotFoundError import KeyNotFoundError +from topics.models import ( + LearningOutcome, + CurriculumArea, +) + + +class LearningOutcomesLoader(BaseLoader): + """Custom loader for loading learning outcomes.""" + + def __init__(self, structure_file_path, BASE_PATH): + """Create the loader for loading learning outcomes. + + Args: + structure_file_path: File path to YAML file (str). + BASE_PATH: Base file path (str). + """ + super().__init__(BASE_PATH) + self.structure_file_path = structure_file_path + self.BASE_PATH = os.path.join(self.BASE_PATH, os.path.split(structure_file_path)[0]) + + @transaction.atomic + def load(self): + """Load the content for learning outcomes. + + Raise: + MissingRequiredFieldError: when no object can be found with the matching + attribute. + """ + learning_outcomes = self.load_yaml_file( + os.path.join( + self.BASE_PATH, + self.structure_file_path + ) + ) + + for (outcome_slug, outcome_data) in learning_outcomes.items(): + + if ("text" not in outcome_data) or (outcome_data["text"] is None): + raise MissingRequiredFieldError( + self.structure_file_path, + ["text"], + "Learning Outcome" + ) + + # Create outcome objects and save to db + outcome = LearningOutcome( + slug=outcome_slug, + text=outcome_data["text"] + ) + outcome.save() + + # Add curriculum areas + curriculum_area_slugs = outcome_data.get("curriculum-areas", []) + for curriculum_area_slug in curriculum_area_slugs: + try: + curriculum_area = CurriculumArea.objects.get( + slug=curriculum_area_slug + ) + outcome.curriculum_areas.add(curriculum_area) + except: + raise KeyNotFoundError( + self.structure_file_path, + curriculum_area_slug, + "Curriculum Areas" + ) + + self.log("Added learning outcome: {}".format(outcome.__str__())) + + self.log("All learning outcomes loaded!\n") diff --git a/csunplugged/topics/management/commands/_LessonsLoader.py b/csunplugged/topics/management/commands/_LessonsLoader.py new file mode 100644 index 000000000..5ef0c1514 --- /dev/null +++ b/csunplugged/topics/management/commands/_LessonsLoader.py @@ -0,0 +1,256 @@ +"""Custom loader for loading lessons.""" + +import os.path +from utils.BaseLoader import BaseLoader +from utils.convert_heading_tree_to_dict import convert_heading_tree_to_dict +from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError +from utils.errors.KeyNotFoundError import KeyNotFoundError +from utils.errors.InvalidConfigValueError import InvalidConfigValueError + +from topics.models import ( + ProgrammingChallenge, + ProgrammingChallengeNumber, + LearningOutcome, + Resource, + ResourceDescription, +) + + +class LessonsLoader(BaseLoader): + """Custom loader for loading lessons.""" + + def __init__(self, lessons_structure_file_path, topic, unit_plan, BASE_PATH): + """Create the loader for loading lessons. + + Args: + lessons_structure_file_path: file path to lessons yaml file (str). + topic: Object of Topic model (Topic). + unit_plan: Object of UnitPlan model (UnitPlan). + BASE_PATH: Base file path (str). + """ + super().__init__(BASE_PATH) + self.lessons_structure_file_path = lessons_structure_file_path + self.topic = topic + self.unit_plan = unit_plan + + def load(self): + """Load the content for a single lesson. + + Raises: + KeyNotFoundError: when no object can be found with the matching attribute. + InvalidConfigValueError: when provided value is not valid. + MissingRequiredFieldError: when a value for a required model field cannot be + found in the config file. + """ + lessons_structure = self.load_yaml_file(self.lessons_structure_file_path) + + for (lesson_slug, lesson_structure) in lessons_structure.items(): + + if lesson_structure is None: + raise MissingRequiredFieldError( + self.lessons_structure_file_path, + ["number"], + "Lesson" + ) + + # Build the file path to the lesson"s md file + file_path = os.path.join( + self.BASE_PATH, + "lessons", + "{}.md".format(lesson_slug) + ) + + lesson_content = self.convert_md_file( + file_path, + self.lessons_structure_file_path, + ) + + if "computational-thinking-links" in lesson_structure: + file_name = lesson_structure["computational-thinking-links"] + file_path = os.path.join( + self.BASE_PATH, + "lessons", + file_name + ) + ct_links_content = self.convert_md_file( + file_path, + self.lessons_structure_file_path, + heading_required=False, + remove_title=False, + ) + ct_links = ct_links_content.html_string + else: + ct_links = None + + if "duration" in lesson_structure: + lesson_duration = lesson_structure["duration"] + else: + lesson_duration = None + + heading_tree = None + if lesson_content.heading_tree: + heading_tree = convert_heading_tree_to_dict(lesson_content.heading_tree) + + if "programming-challenges-description" in lesson_structure: + file_name = lesson_structure["programming-challenges-description"] + file_path = os.path.join( + self.BASE_PATH, + "lessons", + file_name + ) + programming_description_content = self.convert_md_file( + file_path, + self.lessons_structure_file_path, + heading_required=False, + remove_title=False, + ) + programming_description = programming_description_content.html_string + else: + programming_description = None + + classroom_resources = lesson_structure.get("classroom-resources", None) + if isinstance(classroom_resources, list): + for classroom_resource in classroom_resources: + if not isinstance(classroom_resource, str): + raise InvalidConfigValueError( + self.lessons_structure_file_path, + "classroom-resources list item", + "A string describing the classroom resource." + ) + elif len(classroom_resource) > 100: + raise InvalidConfigValueError( + self.lessons_structure_file_path, + "classroom-resources list item", + "Item description must be less than 100 characters." + ) + elif classroom_resources is not None: + raise InvalidConfigValueError( + self.lessons_structure_file_path, + "classroom-resources", + "List of strings." + ) + + lesson = self.topic.lessons.create( + unit_plan=self.unit_plan, + slug=lesson_slug, + name=lesson_content.title, + duration=lesson_duration, + content=lesson_content.html_string, + computational_thinking_links=ct_links, + heading_tree=heading_tree, + programming_challenges_description=programming_description, + classroom_resources=classroom_resources, + ) + lesson.save() + + # Add programming challenges + if "programming-challenges" in lesson_structure: + programming_challenge_slugs = lesson_structure["programming-challenges"] + if programming_challenge_slugs is not None: + # Check all slugs are valid + for programming_challenge_slug in programming_challenge_slugs: + try: + ProgrammingChallenge.objects.get( + slug=programming_challenge_slug, + topic=self.topic + ) + + except: + raise KeyNotFoundError( + self.lessons_structure_file_path, + programming_challenge_slug, + "Programming Challenges" + ) + + # Store number of challenge in relationship with lesson. + # If three linked challenges have numbers 1.1, 4.2, and 4.5 + # They will be stored as 1.1, 2.1, and 2.2 respectively. + + # Order challenges for numbering. + programming_challenges = ProgrammingChallenge.objects.filter( + slug__in=programming_challenge_slugs, + topic=self.topic + ).order_by("challenge_set_number", "challenge_number") + + # Setup variables for numbering. + display_set_number = 0 + last_set_number = -1 + display_number = 0 + last_number = -1 + + # For each challenge, increment number variables if original + # numbers are different. + for programming_challenge in programming_challenges: + if programming_challenge.challenge_set_number > last_set_number: + display_set_number += 1 + display_number = 0 + last_number = -1 + if programming_challenge.challenge_number > last_number: + display_number += 1 + last_set_number = programming_challenge.challenge_set_number + last_number = programming_challenge.challenge_number + + # Create and save relationship between lesson and + # challenge that contains challenge number. + relationship = ProgrammingChallengeNumber( + programming_challenge=programming_challenge, + lesson=lesson, + challenge_set_number=display_set_number, + challenge_number=display_number, + ) + relationship.save() + + # Add learning outcomes + if "learning-outcomes" in lesson_structure: + learning_outcome_slugs = lesson_structure["learning-outcomes"] + if learning_outcome_slugs is not None: + for learning_outcome_slug in learning_outcome_slugs: + try: + learning_outcome = LearningOutcome.objects.get( + slug=learning_outcome_slug + ) + lesson.learning_outcomes.add(learning_outcome) + except: + raise KeyNotFoundError( + self.lessons_structure_file_path, + learning_outcome_slug, + "Learning Outcomes" + ) + + # Add generated resources + if "generated-resources" in lesson_structure: + resources = lesson_structure["generated-resources"] + if resources is not None: + for (resource_slug, resource_data) in resources.items(): + if resource_data is None: + raise MissingRequiredFieldError( + self.lessons_structure_file_path, + ["description"], + "Generated Resource" + ) + try: + resource = Resource.objects.get( + slug=resource_slug + ) + except: + raise KeyNotFoundError( + self.lessons_structure_file_path, + resource_slug, + "Resources" + ) + resource_description = resource_data.get("description", None) + if resource_description is None: + raise MissingRequiredFieldError( + self.lessons_structure_file_path, + ["description"], + "Generated Resource" + ) + + relationship = ResourceDescription( + resource=resource, + lesson=lesson, + description=resource_description + ) + relationship.save() + + self.log("Added lesson: {}".format(lesson.__str__()), 2) diff --git a/csunplugged/topics/management/commands/_ProgrammingChallengesLoader.py b/csunplugged/topics/management/commands/_ProgrammingChallengesLoader.py new file mode 100644 index 000000000..118c23b86 --- /dev/null +++ b/csunplugged/topics/management/commands/_ProgrammingChallengesLoader.py @@ -0,0 +1,190 @@ +"""Custom loader for loading programming challenges.""" + +import os.path +from utils.BaseLoader import BaseLoader + +from utils.errors.CouldNotFindMarkdownFileError import CouldNotFindMarkdownFileError +from utils.errors.KeyNotFoundError import KeyNotFoundError +from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError + +from topics.models import ( + LearningOutcome, + ProgrammingChallengeDifficulty, + ProgrammingChallengeLanguage, + ProgrammingChallengeImplementation, +) + + +class ProgrammingChallengesLoader(BaseLoader): + """Custom loader for loading programming challenges.""" + + def __init__(self, structure_file_path, topic, BASE_PATH): + """Create the loader for loading programming challenges. + + Args: + structure_file_path: File path for structure YAML file (str). + topic: Object of related topic model (Topic). + BASE_PATH: Base file path (str). + """ + super().__init__(BASE_PATH) + self.structure_file_path = os.path.join(self.BASE_PATH, structure_file_path) + self.BASE_PATH = os.path.join(self.BASE_PATH, os.path.split(structure_file_path)[0]) + self.topic = topic + + def load(self): + """Load the content for programming challenges. + + Raises: + CouldNotFindMarkdownFileError: when no file can be found with the provided filename. + KeyNotFoundError: when no object can be found with the matching attribute. + MissingRequiredFieldError: when no object can be found with the matching + attribute. + """ + programming_challenges_structure = self.load_yaml_file(self.structure_file_path) + + for (challenge_slug, challenge_structure) in programming_challenges_structure.items(): + + if challenge_structure is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["challenge-set-number", "challenge-number", + "programming-languages", "difficulty-level"], + "Programming Challenge" + ) + + # Retrieve required variables from md file + challenge_set_number = challenge_structure.get("challenge-set-number", None) + challenge_number = challenge_structure.get("challenge-number", None) + challenge_languages = challenge_structure.get("programming-languages", None) + challenge_difficulty = challenge_structure.get("difficulty-level", None) + if None in [challenge_set_number, challenge_number, challenge_languages, challenge_difficulty]: + raise MissingRequiredFieldError( + self.structure_file_path, + ["challenge-set-number", "challenge-number", + "programming-languages", "difficulty-level"], + "Programming Challenge" + ) + + # Build the path to the programming challenge's folder + file_path = os.path.join( + self.BASE_PATH, + challenge_slug, + "{}.md" + ) + + challenge_content = self.convert_md_file( + file_path.format(challenge_slug), + self.structure_file_path + ) + + challenge_extra_challenge_file = challenge_structure.get("extra-challenge", None) + if challenge_extra_challenge_file: + challenge_extra_challenge_content = self.convert_md_file( + file_path.format(challenge_extra_challenge_file[:-3]), + self.structure_file_path, + heading_required=False, + ) + challenge_extra_challenge = challenge_extra_challenge_content.html_string + else: + challenge_extra_challenge = None + + try: + difficulty_level = ProgrammingChallengeDifficulty.objects.get( + level=challenge_difficulty + ) + except: + raise KeyNotFoundError( + self.structure_file_path, + challenge_difficulty, + "Programming Challenge Difficulty" + ) + + programming_challenge = self.topic.programming_challenges.create( + slug=challenge_slug, + name=challenge_content.title, + challenge_set_number=challenge_set_number, + challenge_number=challenge_number, + content=challenge_content.html_string, + extra_challenge=challenge_extra_challenge, + difficulty=difficulty_level + ) + programming_challenge.save() + + LOG_TEMPLATE = "Added programming challenge: {}" + self.log(LOG_TEMPLATE.format(programming_challenge.name), 1) + + for language in challenge_languages: + if language is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["challenge-set-number", "challenge-number", + "programming-languages", "difficulty-level"], + "Programming Challenge" + ) + try: + language_object = ProgrammingChallengeLanguage.objects.get( + slug=language + ) + except: + raise KeyNotFoundError( + self.structure_file_path, + language, + "Programming Challenge Language" + ) + + expected_result_content = self.convert_md_file( + file_path.format( + "{}-expected".format(language) + ), + self.structure_file_path, + heading_required=False + ) + + # Load example solution + solution_content = self.convert_md_file( + file_path.format( + "{}-solution".format(language) + ), + self.structure_file_path, + heading_required=False + ) + + # Load hint if given + try: + hint_content = self.convert_md_file( + file_path.format( + "{}-hints".format(language) + ), + self.structure_file_path, + heading_required=False + ) + except CouldNotFindMarkdownFileError: + hint_content = None + + implementation = ProgrammingChallengeImplementation( + expected_result=expected_result_content.html_string, + hints=None if hint_content is None else hint_content.html_string, + solution=solution_content.html_string, + language=language_object, + challenge=programming_challenge, + topic=self.topic + ) + implementation.save() + + LOG_TEMPLATE = "Added language implementation: {}" + self.log(LOG_TEMPLATE.format(implementation.language), 2) + + if "learning-outcomes" in challenge_structure: + learning_outcomes = challenge_structure["learning-outcomes"] + if learning_outcomes is not None: + for learning_outcome_slug in learning_outcomes: + try: + learning_outcome = LearningOutcome.objects.get( + slug=learning_outcome_slug + ) + programming_challenge.learning_outcomes.add(learning_outcome) + except: + raise KeyNotFoundError( + self.structure_file_path, + learning_outcome_slug, + "Learning Outcome") diff --git a/csunplugged/topics/management/commands/_ProgrammingChallengesStructureLoader.py b/csunplugged/topics/management/commands/_ProgrammingChallengesStructureLoader.py new file mode 100644 index 000000000..1906b2bcf --- /dev/null +++ b/csunplugged/topics/management/commands/_ProgrammingChallengesStructureLoader.py @@ -0,0 +1,112 @@ +"""Custom loader for loading structure of programming challenges.""" + +import os.path +from django.db import transaction + +from utils.BaseLoader import BaseLoader + +from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError + +from topics.models import ProgrammingChallengeLanguage, ProgrammingChallengeDifficulty + + +class ProgrammingChallengesStructureLoader(BaseLoader): + """Custom loader for loading structure of programming challenges.""" + + def __init__(self, structure_file_path, BASE_PATH): + """Create the loader for loading structure of programming challenges. + + Args: + structure_file_path: File path for structure YAML file (str). + BASE_PATH: Base file path (str). + """ + super().__init__(BASE_PATH) + self.structure_file_path = structure_file_path + self.BASE_PATH = os.path.join(self.BASE_PATH, os.path.split(structure_file_path)[0]) + + @transaction.atomic + def load(self): + """Load the content for structure of programming challenges. + + Raises: + MissingRequiredFieldError: when no object can be found with the matching + attribute. + """ + structure = self.load_yaml_file( + os.path.join( + self.BASE_PATH, + self.structure_file_path + ) + ) + + languages = structure.get("languages", None) + difficulty_levels = structure.get("difficulties", None) + if None in [languages, difficulty_levels]: + raise MissingRequiredFieldError( + self.structure_file_path, + ["lanugages", "difficulties"], + "Programming Challenge Structure" + ) + + for (language, language_data) in languages.items(): + + if language_data is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["name", "number"], + "Programming Challenge Language" + ) + + # Check for required fields + language_name = language_data.get("name", None) + language_number = language_data.get("number", None) + if language_name is None or language_number is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["name", "number"], + "Programming Challenge Language" + ) + + # Check if icon is given + if "icon" in language_data: + language_icon = language_data["icon"] + else: + language_icon = None + + new_language = ProgrammingChallengeLanguage( + slug=language, + name=language_name, + number=language_number, + icon=language_icon + ) + + new_language.save() + + self.log("Added programming langauge: {}".format(new_language.__str__())) + + for (difficulty, difficulty_data) in difficulty_levels.items(): + + if difficulty_data is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["name"], + "Programming Challenge Difficulty" + ) + + difficulty_name = difficulty_data.get("name", None) + if difficulty_name is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["name"], + "Programming Challenge Difficulty" + ) + + new_difficulty = ProgrammingChallengeDifficulty( + level=difficulty, + name=difficulty_name + ) + new_difficulty.save() + + self.log("Added programming difficulty level: {}".format(new_difficulty.__str__())) + + self.log("") diff --git a/csunplugged/topics/management/commands/_TopicLoader.py b/csunplugged/topics/management/commands/_TopicLoader.py new file mode 100644 index 000000000..16bbc3fe4 --- /dev/null +++ b/csunplugged/topics/management/commands/_TopicLoader.py @@ -0,0 +1,124 @@ +"""Custom loader for loading a topic.""" + +import os.path +from django.db import transaction + +from utils.BaseLoader import BaseLoader +from utils.check_required_files import find_image_files + +from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError + +from topics.models import Topic + + +class TopicLoader(BaseLoader): + """Custom loader for loading a topic.""" + + def __init__(self, factory, structure_file_path, BASE_PATH): + """Create the loader for loading a topic. + + Args: + factory: LoaderFactory object for creating loaders (LoaderFactory). + structure_file_path: File path for structure YAML file (str). + BASE_PATH: Base file path (str). + """ + super().__init__(BASE_PATH) + self.factory = factory + self.topic_slug = os.path.split(structure_file_path)[0] + self.structure_file_path = os.path.join(self.BASE_PATH, structure_file_path) + self.BASE_PATH = os.path.join(self.BASE_PATH, self.topic_slug) + + @transaction.atomic + def load(self): + """Load the content for a topic. + + Raise: + MissingRequiredFieldError: when no object can be found with the matching + attribute. + """ + topic_structure = self.load_yaml_file(self.structure_file_path) + + unit_plans = topic_structure.get("unit-plans", None) + if unit_plans is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["unit-plans"], + "Topic" + ) + + # Convert the content to HTML + topic_content = self.convert_md_file( + os.path.join( + self.BASE_PATH, + "{}.md".format(self.topic_slug) + ), + self.structure_file_path + ) + + # If other resources are given, convert to HTML + if "other-resources" in topic_structure: + topic_other_resources_file = topic_structure["other-resources"] + if topic_other_resources_file is not None: + other_resources_content = self.convert_md_file( + os.path.join( + self.BASE_PATH, + topic_other_resources_file + ), + self.structure_file_path + ) + topic_other_resources_html = other_resources_content.html_string + else: + topic_other_resources_html = None + else: + topic_other_resources_html = None + + # Check if icon is given + if "icon" in topic_structure: + topic_icon = topic_structure["icon"] + if topic_icon is not None: + find_image_files([topic_icon], self.structure_file_path) + else: + topic_icon = None + else: + topic_icon = None + + # Create topic objects and save to the db + topic = Topic( + slug=self.topic_slug, + name=topic_content.title, + content=topic_content.html_string, + other_resources=topic_other_resources_html, + icon=topic_icon + ) + topic.save() + + self.log("Added Topic: {}".format(topic.name)) + + # Load programming challenges + if "programming-challenges" in topic_structure: + programming_challenges_structure_file_path = topic_structure["programming-challenges"] + if programming_challenges_structure_file_path is not None: + self.factory.create_programming_challenges_loader( + programming_challenges_structure_file_path, + topic, + self.BASE_PATH + ).load() + + # Load unit plans + for unit_plan_file_path in unit_plans: + self.factory.create_unit_plan_loader( + unit_plan_file_path, + topic, + self.BASE_PATH + ).load() + + if "curriculum-integrations" in topic_structure: + curriculum_integrations_structure_file_path = topic_structure["curriculum-integrations"] + if curriculum_integrations_structure_file_path is not None: + self.factory.create_curriculum_integrations_loader( + curriculum_integrations_structure_file_path, + topic, + self.BASE_PATH + ).load() + + self.log("") diff --git a/csunplugged/topics/management/commands/_UnitPlanLoader.py b/csunplugged/topics/management/commands/_UnitPlanLoader.py new file mode 100644 index 000000000..5d08cc340 --- /dev/null +++ b/csunplugged/topics/management/commands/_UnitPlanLoader.py @@ -0,0 +1,159 @@ +"""Custom loader for loading unit plans.""" + +import os.path +from utils.BaseLoader import BaseLoader +from utils.convert_heading_tree_to_dict import convert_heading_tree_to_dict +from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError +from utils.errors.KeyNotFoundError import KeyNotFoundError + +from topics.models import ( + Lesson, + LessonNumber, + AgeGroup, +) + + +class UnitPlanLoader(BaseLoader): + """Custom loader for loading unit plans.""" + + def __init__(self, factory, structure_file_path, topic, BASE_PATH): + """Create the loader for loading unit plans. + + Args: + factory: LoaderFactory object for creating loaders (LoaderFactory). + structure_file_path: File path for structure YAML file (str). + topic: Object of related topic model (Topic). + BASE_PATH: Base file path (str). + """ + super().__init__(BASE_PATH) + self.factory = factory + self.unit_plan_slug = os.path.split(structure_file_path)[0] + self.structure_file_path = os.path.join(self.BASE_PATH, structure_file_path) + self.BASE_PATH = os.path.join(self.BASE_PATH, self.unit_plan_slug) + self.topic = topic + + def load(self): + """Load the content for unit plans. + + Raise: + KeyNotFoundError: when no object can be found with the matching attribute. + MissingRequiredFieldError: when a value for a required model field cannot + be found in the config file. + """ + unit_plan_structure = self.load_yaml_file(self.structure_file_path) + + # Convert the content to HTML + unit_plan_content = self.convert_md_file( + os.path.join( + self.BASE_PATH, + "{}.md".format(self.unit_plan_slug) + ), + self.structure_file_path + ) + + heading_tree = None + if unit_plan_content.heading_tree: + heading_tree = convert_heading_tree_to_dict(unit_plan_content.heading_tree) + + if "computational-thinking-links" in unit_plan_structure: + file_name = unit_plan_structure["computational-thinking-links"] + file_path = os.path.join( + self.BASE_PATH, + file_name + ) + ct_links_content = self.convert_md_file( + file_path, + self.structure_file_path, + heading_required=False, + remove_title=False, + ) + ct_links = ct_links_content.html_string + else: + ct_links = None + + unit_plan = self.topic.unit_plans.create( + slug=self.unit_plan_slug, + name=unit_plan_content.title, + content=unit_plan_content.html_string, + heading_tree=heading_tree, + computational_thinking_links=ct_links, + ) + unit_plan.save() + + self.log("Added unit plan: {}".format(unit_plan.name), 1) + + # Load the lessons for the unit plan + # Get path to lesson yaml + lessons_yaml = unit_plan_structure.get("lessons", None) + if lessons_yaml is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["lessons", "age-groups"], + "Unit Plan" + ) + lessons_structure_file_path = os.path.join(self.BASE_PATH, lessons_yaml) + + # Call the loader to save the lessons into the db + self.factory.create_lessons_loader( + lessons_structure_file_path, + self.topic, + unit_plan, + self.BASE_PATH + ).load() + + # Create AgeGroup and assign to lessons + age_groups = unit_plan_structure.get("age-groups", None) + if age_groups is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["lessons", "age-groups"], + "Unit Plan" + ) + + for (age_group_slug, age_group_data) in age_groups.items(): + + try: + age_group = AgeGroup.objects.get( + slug=age_group_slug + ) + except: + raise KeyNotFoundError( + self.structure_file_path, + age_group_slug, + "Age Range" + ) + + if age_group_data is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["lesson keys"], + "Unit Plan" + ) + + for (lesson_slug, lesson_data) in age_group_data.items(): + try: + lesson = Lesson.objects.get( + slug=lesson_slug + ) + except: + raise KeyNotFoundError( + self.structure_file_path, + lesson_slug, + "Lesson" + ) + + if lesson_data is None or lesson_data.get("number", None) is None: + raise MissingRequiredFieldError( + self.structure_file_path, + ["number"], + "Unit Plan" + ) + else: + lesson_number = lesson_data.get("number", None) + + relationship = LessonNumber( + age_group=age_group, + lesson=lesson, + number=lesson_number, + ) + relationship.save() diff --git a/csunplugged/topics/management/commands/__init__.py b/csunplugged/topics/management/commands/__init__.py new file mode 100644 index 000000000..b8101959b --- /dev/null +++ b/csunplugged/topics/management/commands/__init__.py @@ -0,0 +1 @@ +"""Module for the custom commands for the topics appliation.""" diff --git a/csunplugged/topics/management/commands/loadtopics.py b/csunplugged/topics/management/commands/loadtopics.py new file mode 100644 index 000000000..eb1d9d9c1 --- /dev/null +++ b/csunplugged/topics/management/commands/loadtopics.py @@ -0,0 +1,94 @@ +"""Module for the custom Django loadtopics command.""" + +import os.path +from django.core.management.base import BaseCommand + +from utils.BaseLoader import BaseLoader +from utils.LoaderFactory import LoaderFactory +from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError + + +class Command(BaseCommand): + """Required command class for the custom Django loadtopics command.""" + + help = "Converts Markdown files listed in structure file and stores" + + def handle(self, *args, **options): + """Automatically called when the loadresources command is given. + + Raise: + MissingRequiredFieldError: when no object can be found with the matching + attribute. + """ + factory = LoaderFactory() + # Get structure and content files + base_loader = BaseLoader() + BASE_PATH = "topics/content/en/" + + structure_file_path = os.path.join( + BASE_PATH, + "structure.yaml" + ) + + structure_file = base_loader.load_yaml_file(structure_file_path) + + if "curriculum-areas" in structure_file: + curriculum_areas_structure_file_path = structure_file["curriculum-areas"] + if curriculum_areas_structure_file_path is not None: + factory.create_curriculum_areas_loader( + curriculum_areas_structure_file_path, + BASE_PATH + ).load() + + if "learning-outcomes" in structure_file: + learning_outcomes_structure_file_path = structure_file["learning-outcomes"] + if learning_outcomes_structure_file_path is not None: + factory.create_learning_outcomes_loader( + learning_outcomes_structure_file_path, + BASE_PATH + ).load() + + if "programming-challenges-structure" in structure_file: + programming_challenges_structure_file_path = structure_file["programming-challenges-structure"] + if programming_challenges_structure_file_path is not None: + factory.create_programming_challenges_structure_loader( + programming_challenges_structure_file_path, + BASE_PATH + ).load() + + if "glossary-folder" in structure_file: + glossary_folder_path = structure_file["glossary-folder"] + if glossary_folder_path is not None: + factory.create_glossary_terms_loader( + glossary_folder_path, + structure_file_path, + BASE_PATH + ).load() + + if structure_file["age-groups"] is None: + raise MissingRequiredFieldError( + structure_file_path, + ["age-groups"], + "Application Structure" + ) + else: + age_groups_path = structure_file["age-groups"] + if age_groups_path is not None: + factory.create_age_groups_loader( + age_groups_path, + BASE_PATH + ).load() + + if structure_file["topics"] is None: + raise MissingRequiredFieldError( + structure_file_path, + ["topics"], + "Application Structure" + ) + + for topic in structure_file["topics"]: + topic_structure_file = "{0}/{0}.yaml".format(topic) + factory.create_topic_loader( + topic_structure_file, + BASE_PATH + ).load() diff --git a/csunplugged/topics/migrations/0001_initial.py b/csunplugged/topics/migrations/0001_initial.py new file mode 100644 index 000000000..0b051620f --- /dev/null +++ b/csunplugged/topics/migrations/0001_initial.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-01-28 04:21 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Topic', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200)), + ('description', models.TextField()), + ], + ), + ] diff --git a/csunplugged/topics/migrations/0002_topic_slug.py b/csunplugged/topics/migrations/0002_topic_slug.py new file mode 100644 index 000000000..994921485 --- /dev/null +++ b/csunplugged/topics/migrations/0002_topic_slug.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-01-29 07:04 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='topic', + name='slug', + field=models.SlugField(default='default-slug'), + preserve_default=False, + ), + ] diff --git a/csunplugged/topics/migrations/0003_pin_db_tables.py b/csunplugged/topics/migrations/0003_pin_db_tables.py new file mode 100644 index 000000000..7b8bd1353 --- /dev/null +++ b/csunplugged/topics/migrations/0003_pin_db_tables.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-01-30 00:37 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0002_topic_slug'), + ] + + operations = [ + migrations.AlterModelTable( + name='topic', + table='topics_topic', + ), + ] diff --git a/csunplugged/topics/migrations/0004_rename_tables.py b/csunplugged/topics/migrations/0004_rename_tables.py new file mode 100644 index 000000000..c4c2b76ff --- /dev/null +++ b/csunplugged/topics/migrations/0004_rename_tables.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-01-30 00:54 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0003_pin_db_tables'), + ] + + operations = [ + migrations.AlterModelTable( + name='topic', + table=None, + ), + ] diff --git a/csunplugged/topics/migrations/0005_auto_20170130_0122.py b/csunplugged/topics/migrations/0005_auto_20170130_0122.py new file mode 100644 index 000000000..ca4ea926a --- /dev/null +++ b/csunplugged/topics/migrations/0005_auto_20170130_0122.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-01-30 01:22 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0004_rename_tables'), + ] + + operations = [ + migrations.AlterField( + model_name='topic', + name='slug', + field=models.SlugField(unique=True), + ), + ] diff --git a/csunplugged/topics/migrations/0006_auto_20170203_0635.py b/csunplugged/topics/migrations/0006_auto_20170203_0635.py new file mode 100644 index 000000000..caa0617b6 --- /dev/null +++ b/csunplugged/topics/migrations/0006_auto_20170203_0635.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-03 06:35 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0005_auto_20170130_0122'), + ] + + operations = [ + migrations.CreateModel( + name='CurriculumLink', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, unique=True)), + ], + ), + migrations.CreateModel( + name='FollowUpActivity', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('slug', models.SlugField()), + ('name', models.CharField(max_length=200)), + ('content', models.TextField()), + ('curriculum_links', models.ManyToManyField(to='topics.CurriculumLink')), + ], + ), + migrations.CreateModel( + name='LearningOutcome', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('text', models.CharField(max_length=200, unique=True)), + ], + ), + migrations.CreateModel( + name='Lesson', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('slug', models.SlugField()), + ('name', models.CharField(max_length=100)), + ('number', models.IntegerField()), + ('age_bracket', models.CharField(max_length=10)), + ('content', models.TextField()), + ('curriculum_links', models.ManyToManyField(to='topics.CurriculumLink')), + ('learning_outcomes', models.ManyToManyField(to='topics.LearningOutcome')), + ], + ), + migrations.CreateModel( + name='ProgrammingExercise', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('slug', models.SlugField()), + ('name', models.CharField(max_length=200)), + ('content', models.TextField()), + ('scratch_hints', models.TextField()), + ('scratch_solution', models.TextField()), + ('python_hints', models.TextField()), + ('python_solution', models.TextField()), + ('learning_outcomes', models.ManyToManyField(to='topics.LearningOutcome')), + ], + ), + migrations.CreateModel( + name='UnitPlan', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('slug', models.SlugField()), + ('name', models.CharField(max_length=100)), + ('content', models.TextField()), + ], + ), + migrations.RenameField( + model_name='topic', + old_name='description', + new_name='content', + ), + migrations.AddField( + model_name='topic', + name='icon', + field=models.CharField(default='placeholder.png', max_length=100), + preserve_default=False, + ), + migrations.AlterField( + model_name='topic', + name='name', + field=models.CharField(max_length=100), + ), + migrations.AddField( + model_name='unitplan', + name='topic', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='topics.Topic'), + ), + migrations.AddField( + model_name='programmingexercise', + name='topic', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='topics.Topic'), + ), + migrations.AddField( + model_name='lesson', + name='topic', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='topics.Topic'), + ), + migrations.AddField( + model_name='lesson', + name='unit_plan', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='topics.UnitPlan'), + ), + migrations.AddField( + model_name='followupactivity', + name='topic', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='topics.Topic'), + ), + ] diff --git a/csunplugged/topics/migrations/0007_auto_20170203_0845.py b/csunplugged/topics/migrations/0007_auto_20170203_0845.py new file mode 100644 index 000000000..547f7ccfa --- /dev/null +++ b/csunplugged/topics/migrations/0007_auto_20170203_0845.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-03 08:45 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0006_auto_20170203_0635'), + ] + + operations = [ + migrations.AlterField( + model_name='followupactivity', + name='topic', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='follow_up_activities', to='topics.Topic'), + ), + ] diff --git a/csunplugged/topics/migrations/0008_auto_20170203_0934.py b/csunplugged/topics/migrations/0008_auto_20170203_0934.py new file mode 100644 index 000000000..fc899c6b8 --- /dev/null +++ b/csunplugged/topics/migrations/0008_auto_20170203_0934.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-03 09:34 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0007_auto_20170203_0845'), + ] + + operations = [ + migrations.AlterField( + model_name='followupactivity', + name='curriculum_links', + field=models.ManyToManyField(related_name='follow_up_activity_curriculum_links', to='topics.CurriculumLink'), + ), + migrations.AlterField( + model_name='followupactivity', + name='topic', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='topic_follow_up_activities', to='topics.Topic'), + ), + migrations.AlterField( + model_name='lesson', + name='curriculum_links', + field=models.ManyToManyField(related_name='lesson_curriculum_links', to='topics.CurriculumLink'), + ), + migrations.AlterField( + model_name='lesson', + name='learning_outcomes', + field=models.ManyToManyField(related_name='lesson_learning_outcomes', to='topics.LearningOutcome'), + ), + migrations.AlterField( + model_name='lesson', + name='topic', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='topic_lessons', to='topics.Topic'), + ), + migrations.AlterField( + model_name='lesson', + name='unit_plan', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='unit_plan_lessons', to='topics.UnitPlan'), + ), + migrations.AlterField( + model_name='programmingexercise', + name='learning_outcomes', + field=models.ManyToManyField(related_name='programming_exercise_learning_outcomes', to='topics.LearningOutcome'), + ), + migrations.AlterField( + model_name='programmingexercise', + name='topic', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='topic_programming_exercises', to='topics.Topic'), + ), + migrations.AlterField( + model_name='unitplan', + name='topic', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='topic_unit_plans', to='topics.Topic'), + ), + ] diff --git a/csunplugged/topics/migrations/0009_topic_other_resources.py b/csunplugged/topics/migrations/0009_topic_other_resources.py new file mode 100644 index 000000000..6ffbc02b8 --- /dev/null +++ b/csunplugged/topics/migrations/0009_topic_other_resources.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-05 23:37 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0008_auto_20170203_0934'), + ] + + operations = [ + migrations.AddField( + model_name='topic', + name='other_resources', + field=models.TextField(default=''), + preserve_default=False, + ), + ] diff --git a/csunplugged/topics/migrations/0010_auto_20170208_0246.py b/csunplugged/topics/migrations/0010_auto_20170208_0246.py new file mode 100644 index 000000000..efd19ce76 --- /dev/null +++ b/csunplugged/topics/migrations/0010_auto_20170208_0246.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-08 02:46 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0009_topic_other_resources'), + ] + + operations = [ + migrations.AlterField( + model_name='lesson', + name='age_bracket', + field=models.CharField(max_length=20), + ), + ] diff --git a/csunplugged/topics/migrations/0011_lesson_age_bracket_slug.py b/csunplugged/topics/migrations/0011_lesson_age_bracket_slug.py new file mode 100644 index 000000000..99c5104e0 --- /dev/null +++ b/csunplugged/topics/migrations/0011_lesson_age_bracket_slug.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-08 09:41 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0010_auto_20170208_0246'), + ] + + operations = [ + migrations.AddField( + model_name='lesson', + name='age_bracket_slug', + field=models.CharField(default='', max_length=20), + preserve_default=False, + ), + ] diff --git a/csunplugged/topics/migrations/0012_programmingexercise_exercise_number.py b/csunplugged/topics/migrations/0012_programmingexercise_exercise_number.py new file mode 100644 index 000000000..26077410b --- /dev/null +++ b/csunplugged/topics/migrations/0012_programmingexercise_exercise_number.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-08 20:06 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0011_lesson_age_bracket_slug'), + ] + + operations = [ + migrations.AddField( + model_name='programmingexercise', + name='exercise_number', + field=models.IntegerField(default=1), + preserve_default=False, + ), + ] diff --git a/csunplugged/topics/migrations/0013_auto_20170208_2312.py b/csunplugged/topics/migrations/0013_auto_20170208_2312.py new file mode 100644 index 000000000..8fbdb26b3 --- /dev/null +++ b/csunplugged/topics/migrations/0013_auto_20170208_2312.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-08 23:12 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0012_programmingexercise_exercise_number'), + ] + + operations = [ + migrations.AlterField( + model_name='topic', + name='icon', + field=models.CharField(max_length=100, null=True), + ), + ] diff --git a/csunplugged/topics/migrations/0014_learningoutcome_slug.py b/csunplugged/topics/migrations/0014_learningoutcome_slug.py new file mode 100644 index 000000000..d61dcc3be --- /dev/null +++ b/csunplugged/topics/migrations/0014_learningoutcome_slug.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-21 03:22 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0013_auto_20170208_2312'), + ] + + operations = [ + migrations.AddField( + model_name='learningoutcome', + name='slug', + field=models.SlugField(default='', unique=True), + preserve_default=False, + ), + ] diff --git a/csunplugged/topics/migrations/0015_auto_20170226_2133.py b/csunplugged/topics/migrations/0015_auto_20170226_2133.py new file mode 100644 index 000000000..8cc47158c --- /dev/null +++ b/csunplugged/topics/migrations/0015_auto_20170226_2133.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-26 21:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0014_learningoutcome_slug'), + ] + + operations = [ + migrations.CreateModel( + name='ClassroomResource', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('text', models.CharField(max_length=300, unique=True)), + ], + ), + migrations.AddField( + model_name='lesson', + name='classroom_resources', + field=models.ManyToManyField(related_name='lesson_classroom_resources', to='topics.ClassroomResource'), + ), + ] diff --git a/csunplugged/topics/migrations/0016_auto_20170226_2234.py b/csunplugged/topics/migrations/0016_auto_20170226_2234.py new file mode 100644 index 000000000..2787f2c0f --- /dev/null +++ b/csunplugged/topics/migrations/0016_auto_20170226_2234.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-26 22:34 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('resources', '0004_auto_20170226_0758'), + ('topics', '0015_auto_20170226_2133'), + ] + + operations = [ + migrations.CreateModel( + name='ConnectedGeneratedResource', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('description', models.CharField(max_length=300)), + ('lesson', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='topics.Lesson')), + ('resource', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resources.Resource')), + ], + ), + migrations.AddField( + model_name='lesson', + name='generated_resources', + field=models.ManyToManyField(related_name='lesson_generated_resources', through='topics.ConnectedGeneratedResource', to='resources.Resource'), + ), + ] diff --git a/csunplugged/topics/migrations/0017_auto_20170227_0254.py b/csunplugged/topics/migrations/0017_auto_20170227_0254.py new file mode 100644 index 000000000..4a82a1a65 --- /dev/null +++ b/csunplugged/topics/migrations/0017_auto_20170227_0254.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-27 02:54 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0016_auto_20170226_2234'), + ] + + operations = [ + migrations.CreateModel( + name='Age', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('age', models.PositiveSmallIntegerField()), + ], + ), + migrations.AddField( + model_name='lesson', + name='ages', + field=models.ManyToManyField(related_name='lesson_ages', to='topics.Age'), + ), + ] diff --git a/csunplugged/topics/migrations/0018_auto_20170227_0257.py b/csunplugged/topics/migrations/0018_auto_20170227_0257.py new file mode 100644 index 000000000..c6f11b121 --- /dev/null +++ b/csunplugged/topics/migrations/0018_auto_20170227_0257.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-27 02:57 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0017_auto_20170227_0254'), + ] + + operations = [ + migrations.RemoveField( + model_name='lesson', + name='age_bracket', + ), + migrations.RemoveField( + model_name='lesson', + name='age_bracket_slug', + ), + ] diff --git a/csunplugged/topics/migrations/0019_programmingexercisedifficulty.py b/csunplugged/topics/migrations/0019_programmingexercisedifficulty.py new file mode 100644 index 000000000..391aac3cb --- /dev/null +++ b/csunplugged/topics/migrations/0019_programmingexercisedifficulty.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-28 03:17 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0018_auto_20170227_0257'), + ] + + operations = [ + migrations.CreateModel( + name='ProgrammingExerciseDifficulty', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('level', models.PositiveSmallIntegerField(unique=True)), + ('name', models.CharField(max_length=100)), + ], + ), + ] diff --git a/csunplugged/topics/migrations/0020_auto_20170228_0328.py b/csunplugged/topics/migrations/0020_auto_20170228_0328.py new file mode 100644 index 000000000..ec316dce4 --- /dev/null +++ b/csunplugged/topics/migrations/0020_auto_20170228_0328.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-28 03:28 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0019_programmingexercisedifficulty'), + ] + + operations = [ + migrations.AlterField( + model_name='programmingexercisedifficulty', + name='name', + field=models.CharField(max_length=100, unique=True), + ), + ] diff --git a/csunplugged/topics/migrations/0021_programmingexercise_difficulty.py b/csunplugged/topics/migrations/0021_programmingexercise_difficulty.py new file mode 100644 index 000000000..d9e9f147d --- /dev/null +++ b/csunplugged/topics/migrations/0021_programmingexercise_difficulty.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.5 on 2017-02-28 03:52 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0020_auto_20170228_0328'), + ] + + operations = [ + migrations.AddField( + model_name='programmingexercise', + name='difficulty', + field=models.ForeignKey(default='', on_delete=django.db.models.deletion.CASCADE, related_name='difficulty_programming_exercises', to='topics.ProgrammingExerciseDifficulty'), + preserve_default=False, + ), + ] diff --git a/csunplugged/topics/migrations/0022_auto_20170306_0242.py b/csunplugged/topics/migrations/0022_auto_20170306_0242.py new file mode 100644 index 000000000..89f6bb3af --- /dev/null +++ b/csunplugged/topics/migrations/0022_auto_20170306_0242.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-06 02:42 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0021_programmingexercise_difficulty'), + ] + + operations = [ + migrations.CreateModel( + name='ProgrammingExerciseLanguage', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200)), + ('icon', models.CharField(max_length=100, null=True)), + ], + ), + migrations.CreateModel( + name='ProgrammingExerciseLanguageSolution', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('slug', models.SlugField()), + ('hints', models.TextField()), + ('solution', models.TextField()), + ], + ), + migrations.RemoveField( + model_name='programmingexercise', + name='python_hints', + ), + migrations.RemoveField( + model_name='programmingexercise', + name='python_solution', + ), + migrations.RemoveField( + model_name='programmingexercise', + name='scratch_hints', + ), + migrations.RemoveField( + model_name='programmingexercise', + name='scratch_solution', + ), + migrations.AddField( + model_name='programmingexerciselanguagesolution', + name='exercise', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='solutions', to='topics.ProgrammingExercise'), + ), + migrations.AddField( + model_name='programmingexerciselanguagesolution', + name='language', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='solutions', to='topics.ProgrammingExerciseLanguage'), + ), + ] diff --git a/csunplugged/topics/migrations/0023_programmingexerciselanguage_slug.py b/csunplugged/topics/migrations/0023_programmingexerciselanguage_slug.py new file mode 100644 index 000000000..acf88f188 --- /dev/null +++ b/csunplugged/topics/migrations/0023_programmingexerciselanguage_slug.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-06 03:34 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0022_auto_20170306_0242'), + ] + + operations = [ + migrations.AddField( + model_name='programmingexerciselanguage', + name='slug', + field=models.SlugField(default=''), + preserve_default=False, + ), + ] diff --git a/csunplugged/topics/migrations/0024_auto_20170306_2200.py b/csunplugged/topics/migrations/0024_auto_20170306_2200.py new file mode 100644 index 000000000..93d4913a3 --- /dev/null +++ b/csunplugged/topics/migrations/0024_auto_20170306_2200.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-06 22:00 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0023_programmingexerciselanguage_slug'), + ] + + operations = [ + migrations.RemoveField( + model_name='programmingexerciselanguagesolution', + name='slug', + ), + migrations.AddField( + model_name='programmingexerciselanguagesolution', + name='topic', + field=models.ForeignKey(default='', on_delete=django.db.models.deletion.CASCADE, related_name='solution', to='topics.Topic'), + preserve_default=False, + ), + migrations.AlterField( + model_name='programmingexerciselanguagesolution', + name='exercise', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='solution', to='topics.ProgrammingExercise'), + ), + migrations.AlterField( + model_name='programmingexerciselanguagesolution', + name='language', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='solution', to='topics.ProgrammingExerciseLanguage'), + ), + ] diff --git a/csunplugged/topics/migrations/0025_auto_20170307_0343.py b/csunplugged/topics/migrations/0025_auto_20170307_0343.py new file mode 100644 index 000000000..db19f5178 --- /dev/null +++ b/csunplugged/topics/migrations/0025_auto_20170307_0343.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-07 03:43 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0024_auto_20170306_2200'), + ] + + operations = [ + migrations.CreateModel( + name='ProgrammingExerciseLanguageImplementation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('hints', models.TextField()), + ('solution', models.TextField()), + ('exercise', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='implementations', to='topics.ProgrammingExercise')), + ('language', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='implementations', to='topics.ProgrammingExerciseLanguage')), + ('topic', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='implementations', to='topics.Topic')), + ], + ), + migrations.RemoveField( + model_name='programmingexerciselanguagesolution', + name='exercise', + ), + migrations.RemoveField( + model_name='programmingexerciselanguagesolution', + name='language', + ), + migrations.RemoveField( + model_name='programmingexerciselanguagesolution', + name='topic', + ), + migrations.DeleteModel( + name='ProgrammingExerciseLanguageSolution', + ), + ] diff --git a/csunplugged/topics/migrations/0026_auto_20170309_0044.py b/csunplugged/topics/migrations/0026_auto_20170309_0044.py new file mode 100644 index 000000000..baccdbf66 --- /dev/null +++ b/csunplugged/topics/migrations/0026_auto_20170309_0044.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- # Generated by Django 1.10.6 on 2017-03-09 00:44 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0025_auto_20170307_0343'), + ] + + operations = [ + migrations.RemoveField( + model_name='lesson', + name='ages', + ), + migrations.AddField( + model_name='lesson', + name='max_age', + field=models.PositiveSmallIntegerField(default=1), + preserve_default=False, + ), + migrations.AddField( + model_name='lesson', + name='min_age', + field=models.PositiveSmallIntegerField(default=1), + preserve_default=False, + ), + migrations.DeleteModel( + name='Age', + ), + ] diff --git a/csunplugged/topics/migrations/0027_auto_20170316_0033.py b/csunplugged/topics/migrations/0027_auto_20170316_0033.py new file mode 100644 index 000000000..a5517d134 --- /dev/null +++ b/csunplugged/topics/migrations/0027_auto_20170316_0033.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-16 00:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0026_auto_20170309_0044'), + ] + + operations = [ + migrations.AddField( + model_name='programmingexercise', + name='exercise_set_number', + field=models.PositiveSmallIntegerField(default=1), + preserve_default=False, + ), + migrations.AlterField( + model_name='programmingexercise', + name='exercise_number', + field=models.PositiveSmallIntegerField(), + ), + ] diff --git a/csunplugged/topics/migrations/0027_curriculumlink_slug.py b/csunplugged/topics/migrations/0027_curriculumlink_slug.py new file mode 100644 index 000000000..2e13e9235 --- /dev/null +++ b/csunplugged/topics/migrations/0027_curriculumlink_slug.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-12 22:57 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0026_auto_20170309_0044'), + ] + + operations = [ + migrations.AddField( + model_name='curriculumlink', + name='slug', + field=models.SlugField(default='default', unique=True), + preserve_default=False, + ), + ] diff --git a/csunplugged/topics/migrations/0028_followupactivity_number.py b/csunplugged/topics/migrations/0028_followupactivity_number.py new file mode 100644 index 000000000..0a9cac0ed --- /dev/null +++ b/csunplugged/topics/migrations/0028_followupactivity_number.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-14 03:57 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0027_curriculumlink_slug'), + ] + + operations = [ + migrations.AddField( + model_name='followupactivity', + name='number', + field=models.PositiveSmallIntegerField(default=1), + preserve_default=False, + ), + ] diff --git a/csunplugged/topics/migrations/0028_programmingexerciselanguageimplementation_expected_result.py b/csunplugged/topics/migrations/0028_programmingexerciselanguageimplementation_expected_result.py new file mode 100644 index 000000000..834aab611 --- /dev/null +++ b/csunplugged/topics/migrations/0028_programmingexerciselanguageimplementation_expected_result.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-16 00:59 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0027_auto_20170316_0033'), + ] + + operations = [ + migrations.AddField( + model_name='programmingexerciselanguageimplementation', + name='expected_result', + field=models.TextField(default=''), + preserve_default=False, + ), + ] diff --git a/csunplugged/topics/migrations/0029_merge_20170316_0302.py b/csunplugged/topics/migrations/0029_merge_20170316_0302.py new file mode 100644 index 000000000..c1747e0ad --- /dev/null +++ b/csunplugged/topics/migrations/0029_merge_20170316_0302.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-16 03:02 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0028_followupactivity_number'), + ('topics', '0028_programmingexerciselanguageimplementation_expected_result'), + ] + + operations = [ + ] diff --git a/csunplugged/topics/migrations/0030_auto_20170318_0214.py b/csunplugged/topics/migrations/0030_auto_20170318_0214.py new file mode 100644 index 000000000..8160cc45d --- /dev/null +++ b/csunplugged/topics/migrations/0030_auto_20170318_0214.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-18 02:14 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0029_merge_20170316_0302'), + ] + + operations = [ + migrations.CreateModel( + name='CurriculumIntegration', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('slug', models.SlugField()), + ('number', models.PositiveSmallIntegerField()), + ('name', models.CharField(max_length=200)), + ('content', models.TextField()), + ], + ), + migrations.RenameModel( + old_name='CurriculumLink', + new_name='CurriculumArea', + ), + migrations.RemoveField( + model_name='followupactivity', + name='curriculum_links', + ), + migrations.RemoveField( + model_name='followupactivity', + name='topic', + ), + migrations.RemoveField( + model_name='lesson', + name='curriculum_links', + ), + migrations.AddField( + model_name='lesson', + name='curriculum_areas', + field=models.ManyToManyField(related_name='lesson_curriculum_areas', to='topics.CurriculumArea'), + ), + migrations.DeleteModel( + name='FollowUpActivity', + ), + migrations.AddField( + model_name='curriculumintegration', + name='curriculum_areas', + field=models.ManyToManyField(related_name='curriculum_integrations', to='topics.CurriculumArea'), + ), + migrations.AddField( + model_name='curriculumintegration', + name='topic', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='curriculum_integrations', to='topics.Topic'), + ), + ] diff --git a/csunplugged/topics/migrations/0031_lesson_programming_exercises.py b/csunplugged/topics/migrations/0031_lesson_programming_exercises.py new file mode 100644 index 000000000..fc2bf65ab --- /dev/null +++ b/csunplugged/topics/migrations/0031_lesson_programming_exercises.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-18 03:53 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0030_auto_20170318_0214'), + ] + + operations = [ + migrations.AddField( + model_name='lesson', + name='programming_exercises', + field=models.ManyToManyField(related_name='lessons', to='topics.ProgrammingExercise'), + ), + ] diff --git a/csunplugged/topics/migrations/0032_curriculumintegration_prerequisite_lessons.py b/csunplugged/topics/migrations/0032_curriculumintegration_prerequisite_lessons.py new file mode 100644 index 000000000..65a05b5ad --- /dev/null +++ b/csunplugged/topics/migrations/0032_curriculumintegration_prerequisite_lessons.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-23 05:19 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0031_lesson_programming_exercises'), + ] + + operations = [ + migrations.AddField( + model_name='curriculumintegration', + name='prerequisite_lessons', + field=models.ManyToManyField(related_name='curriculum_integrations', to='topics.Lesson'), + ), + ] diff --git a/csunplugged/topics/migrations/0033_curriculumarea_parent.py b/csunplugged/topics/migrations/0033_curriculumarea_parent.py new file mode 100644 index 000000000..b03ed6848 --- /dev/null +++ b/csunplugged/topics/migrations/0033_curriculumarea_parent.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-27 01:53 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0032_curriculumintegration_prerequisite_lessons'), + ] + + operations = [ + migrations.AddField( + model_name='curriculumarea', + name='parent', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_curriculum_area', to='topics.CurriculumArea'), + ), + ] diff --git a/csunplugged/topics/migrations/0034_auto_20170327_0200.py b/csunplugged/topics/migrations/0034_auto_20170327_0200.py new file mode 100644 index 000000000..fa577cc60 --- /dev/null +++ b/csunplugged/topics/migrations/0034_auto_20170327_0200.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-27 02:00 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0033_curriculumarea_parent'), + ] + + operations = [ + migrations.RemoveField( + model_name='curriculumarea', + name='parent', + ), + migrations.AddField( + model_name='curriculumarea', + name='parent', + field=models.ManyToManyField(related_name='_curriculumarea_parent_+', to='topics.CurriculumArea'), + ), + ] diff --git a/csunplugged/topics/migrations/0035_auto_20170327_0205.py b/csunplugged/topics/migrations/0035_auto_20170327_0205.py new file mode 100644 index 000000000..e449f7df3 --- /dev/null +++ b/csunplugged/topics/migrations/0035_auto_20170327_0205.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-27 02:05 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0034_auto_20170327_0200'), + ] + + operations = [ + migrations.RemoveField( + model_name='curriculumarea', + name='parent', + ), + migrations.AddField( + model_name='curriculumarea', + name='parent', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_curriculum_area', to='topics.CurriculumArea'), + ), + ] diff --git a/csunplugged/topics/migrations/0036_auto_20170327_0229.py b/csunplugged/topics/migrations/0036_auto_20170327_0229.py new file mode 100644 index 000000000..4eed6b313 --- /dev/null +++ b/csunplugged/topics/migrations/0036_auto_20170327_0229.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-27 02:29 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0035_auto_20170327_0205'), + ] + + operations = [ + migrations.RemoveField( + model_name='curriculumarea', + name='parent', + ), + migrations.AddField( + model_name='curriculumarea', + name='prerequisite_lessons', + field=models.ManyToManyField(related_name='_curriculumarea_prerequisite_lessons_+', to='topics.CurriculumArea'), + ), + ] diff --git a/csunplugged/topics/migrations/0037_auto_20170327_0231.py b/csunplugged/topics/migrations/0037_auto_20170327_0231.py new file mode 100644 index 000000000..97eb824c9 --- /dev/null +++ b/csunplugged/topics/migrations/0037_auto_20170327_0231.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-27 02:31 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0036_auto_20170327_0229'), + ] + + operations = [ + migrations.RemoveField( + model_name='curriculumarea', + name='prerequisite_lessons', + ), + migrations.AddField( + model_name='curriculumarea', + name='children', + field=models.ManyToManyField(related_name='_curriculumarea_children_+', to='topics.CurriculumArea'), + ), + ] diff --git a/csunplugged/topics/migrations/0038_auto_20170327_0813.py b/csunplugged/topics/migrations/0038_auto_20170327_0813.py new file mode 100644 index 000000000..89ec3bbcf --- /dev/null +++ b/csunplugged/topics/migrations/0038_auto_20170327_0813.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-27 08:13 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0037_auto_20170327_0231'), + ] + + operations = [ + migrations.RemoveField( + model_name='curriculumarea', + name='children', + ), + migrations.AddField( + model_name='curriculumarea', + name='children', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_curriculum_area', to='topics.CurriculumArea'), + ), + ] diff --git a/csunplugged/topics/migrations/0039_auto_20170327_0831.py b/csunplugged/topics/migrations/0039_auto_20170327_0831.py new file mode 100644 index 000000000..b8ef2e634 --- /dev/null +++ b/csunplugged/topics/migrations/0039_auto_20170327_0831.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-03-27 08:31 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0038_auto_20170327_0813'), + ] + + operations = [ + migrations.RenameField( + model_name='curriculumarea', + old_name='children', + new_name='parent', + ), + ] diff --git a/csunplugged/topics/migrations/0040_auto_20170403_0635.py b/csunplugged/topics/migrations/0040_auto_20170403_0635.py new file mode 100644 index 000000000..845777fb7 --- /dev/null +++ b/csunplugged/topics/migrations/0040_auto_20170403_0635.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-03 06:35 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0039_auto_20170327_0831'), + ] + + operations = [ + migrations.RemoveField( + model_name='programmingexercise', + name='difficulty', + ), + migrations.AddField( + model_name='programmingexercise', + name='difficulty', + field=models.ManyToManyField(related_name='difficulty_programming_exercises', to='topics.ProgrammingExerciseDifficulty'), + ), + ] diff --git a/csunplugged/topics/migrations/0040_auto_20170409_2120.py b/csunplugged/topics/migrations/0040_auto_20170409_2120.py new file mode 100644 index 000000000..24a95cc1b --- /dev/null +++ b/csunplugged/topics/migrations/0040_auto_20170409_2120.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-09 21:20 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0039_auto_20170327_0831'), + ] + + operations = [ + migrations.AlterField( + model_name='topic', + name='other_resources', + field=models.TextField(null=True), + ), + ] diff --git a/csunplugged/topics/migrations/0041_lesson_duration.py b/csunplugged/topics/migrations/0041_lesson_duration.py new file mode 100644 index 000000000..95601a226 --- /dev/null +++ b/csunplugged/topics/migrations/0041_lesson_duration.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-14 07:18 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0040_auto_20170409_2120'), + ] + + operations = [ + migrations.AddField( + model_name='lesson', + name='duration', + field=models.PositiveSmallIntegerField(null=True), + ), + ] diff --git a/csunplugged/topics/migrations/0042_auto_20170416_2139.py b/csunplugged/topics/migrations/0042_auto_20170416_2139.py new file mode 100644 index 000000000..456cb47b7 --- /dev/null +++ b/csunplugged/topics/migrations/0042_auto_20170416_2139.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-16 21:39 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0041_lesson_duration'), + ] + + operations = [ + migrations.RemoveField( + model_name='lesson', + name='classroom_resources', + ), + migrations.DeleteModel( + name='ClassroomResource', + ), + ] diff --git a/csunplugged/topics/migrations/0043_auto_20170416_2355.py b/csunplugged/topics/migrations/0043_auto_20170416_2355.py new file mode 100644 index 000000000..d96d0a870 --- /dev/null +++ b/csunplugged/topics/migrations/0043_auto_20170416_2355.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-16 23:55 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0042_auto_20170416_2139'), + ] + + operations = [ + migrations.AlterField( + model_name='programmingexerciselanguageimplementation', + name='hints', + field=models.TextField(null=True), + ), + ] diff --git a/csunplugged/topics/migrations/0044_merge_20170423_2100.py b/csunplugged/topics/migrations/0044_merge_20170423_2100.py new file mode 100644 index 000000000..ff1d9afdc --- /dev/null +++ b/csunplugged/topics/migrations/0044_merge_20170423_2100.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.6 on 2017-04-23 21:00 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0043_auto_20170416_2355'), + ('topics', '0040_auto_20170403_0635'), + ] + + operations = [ + ] diff --git a/csunplugged/topics/migrations/0045_auto_20170502_2111.py b/csunplugged/topics/migrations/0045_auto_20170502_2111.py new file mode 100644 index 000000000..525753075 --- /dev/null +++ b/csunplugged/topics/migrations/0045_auto_20170502_2111.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-05-02 21:11 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0044_merge_20170423_2100'), + ] + + operations = [ + migrations.RemoveField( + model_name='programmingexercise', + name='difficulty', + ), + migrations.AddField( + model_name='programmingexercise', + name='difficulty', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='difficulty_programming_exercises', to='topics.ProgrammingExerciseDifficulty'), + preserve_default=False, + ), + ] diff --git a/csunplugged/topics/migrations/0046_glossarydefinition.py b/csunplugged/topics/migrations/0046_glossarydefinition.py new file mode 100755 index 000000000..4d6787f17 --- /dev/null +++ b/csunplugged/topics/migrations/0046_glossarydefinition.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-05-02 03:15 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0045_auto_20170502_2111'), + ] + + operations = [ + migrations.CreateModel( + name='GlossaryTerm', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('slug', models.SlugField(unique=True)), + ('term', models.CharField(max_length=200, unique=True)), + ('definition', models.TextField()), + ], + ), + ] diff --git a/csunplugged/topics/migrations/0047_auto_20170507_0913.py b/csunplugged/topics/migrations/0047_auto_20170507_0913.py new file mode 100644 index 000000000..527b2e468 --- /dev/null +++ b/csunplugged/topics/migrations/0047_auto_20170507_0913.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-07 09:13 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0046_glossarydefinition'), + ] + + operations = [ + migrations.AlterField( + model_name='glossaryterm', + name='term', + field=models.CharField(max_length=200), + ), + ] diff --git a/csunplugged/topics/migrations/0047_auto_20170507_2105.py b/csunplugged/topics/migrations/0047_auto_20170507_2105.py new file mode 100644 index 000000000..7048e303a --- /dev/null +++ b/csunplugged/topics/migrations/0047_auto_20170507_2105.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-07 21:05 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0046_glossarydefinition'), + ] + + operations = [ + migrations.RemoveField( + model_name='lesson', + name='curriculum_areas', + ), + migrations.AddField( + model_name='learningoutcome', + name='curriculum_areas', + field=models.ManyToManyField(related_name='learning_outcomes', to='topics.CurriculumArea'), + ), + ] diff --git a/csunplugged/topics/migrations/0047_curriculumarea_colour.py b/csunplugged/topics/migrations/0047_curriculumarea_colour.py new file mode 100644 index 000000000..c8b6aa54a --- /dev/null +++ b/csunplugged/topics/migrations/0047_curriculumarea_colour.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11 on 2017-05-05 09:20 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0046_glossarydefinition'), + ] + + operations = [ + migrations.AddField( + model_name='curriculumarea', + name='colour', + field=models.CharField(max_length=15, null=True), + ), + ] diff --git a/csunplugged/topics/migrations/0047_programmingexercise_extra_challenge.py b/csunplugged/topics/migrations/0047_programmingexercise_extra_challenge.py new file mode 100644 index 000000000..de1cefcaf --- /dev/null +++ b/csunplugged/topics/migrations/0047_programmingexercise_extra_challenge.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-06 18:55 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0046_glossarydefinition'), + ] + + operations = [ + migrations.AddField( + model_name='programmingexercise', + name='extra_challenge', + field=models.TextField(null=True), + ), + ] diff --git a/csunplugged/topics/migrations/0048_auto_20170507_2226.py b/csunplugged/topics/migrations/0048_auto_20170507_2226.py new file mode 100644 index 000000000..4007aa591 --- /dev/null +++ b/csunplugged/topics/migrations/0048_auto_20170507_2226.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-07 22:26 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0047_auto_20170507_0913'), + ] + + operations = [ + migrations.AlterField( + model_name='glossaryterm', + name='term', + field=models.CharField(max_length=200, null=True, unique=True), + ), + ] diff --git a/csunplugged/topics/migrations/0049_merge_20170508_0231.py b/csunplugged/topics/migrations/0049_merge_20170508_0231.py new file mode 100644 index 000000000..8cb0c519f --- /dev/null +++ b/csunplugged/topics/migrations/0049_merge_20170508_0231.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-08 02:31 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0048_auto_20170507_2226'), + ('topics', '0047_programmingexercise_extra_challenge'), + ] + + operations = [ + ] diff --git a/csunplugged/topics/migrations/0049_merge_20170508_0247.py b/csunplugged/topics/migrations/0049_merge_20170508_0247.py new file mode 100644 index 000000000..3327c8071 --- /dev/null +++ b/csunplugged/topics/migrations/0049_merge_20170508_0247.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-08 02:47 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0047_curriculumarea_colour'), + ('topics', '0048_auto_20170507_2226'), + ] + + operations = [ + ] diff --git a/csunplugged/topics/migrations/0050_merge_20170508_0249.py b/csunplugged/topics/migrations/0050_merge_20170508_0249.py new file mode 100644 index 000000000..63e635e74 --- /dev/null +++ b/csunplugged/topics/migrations/0050_merge_20170508_0249.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-08 02:49 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0049_merge_20170508_0231'), + ('topics', '0049_merge_20170508_0247'), + ] + + operations = [ + ] diff --git a/csunplugged/topics/migrations/0051_lesson_heading_tree.py b/csunplugged/topics/migrations/0051_lesson_heading_tree.py new file mode 100644 index 000000000..6eeb9bade --- /dev/null +++ b/csunplugged/topics/migrations/0051_lesson_heading_tree.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-09 09:45 +from __future__ import unicode_literals + +import django.contrib.postgres.fields.jsonb +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0050_merge_20170508_0249'), + ] + + operations = [ + migrations.AddField( + model_name='lesson', + name='heading_tree', + field=django.contrib.postgres.fields.jsonb.JSONField(null=True), + ), + ] diff --git a/csunplugged/topics/migrations/0051_merge_20170509_0526.py b/csunplugged/topics/migrations/0051_merge_20170509_0526.py new file mode 100644 index 000000000..ebb56efcd --- /dev/null +++ b/csunplugged/topics/migrations/0051_merge_20170509_0526.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-09 05:26 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0050_merge_20170508_0249'), + ('topics', '0047_auto_20170507_2105'), + ] + + operations = [ + ] diff --git a/csunplugged/topics/migrations/0052_merge_20170510_0137.py b/csunplugged/topics/migrations/0052_merge_20170510_0137.py new file mode 100644 index 000000000..18da81134 --- /dev/null +++ b/csunplugged/topics/migrations/0052_merge_20170510_0137.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-10 01:37 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0051_merge_20170509_0526'), + ('topics', '0051_lesson_heading_tree'), + ] + + operations = [ + ] diff --git a/csunplugged/topics/migrations/0053_unitplan_heading_tree.py b/csunplugged/topics/migrations/0053_unitplan_heading_tree.py new file mode 100644 index 000000000..091d6fa8a --- /dev/null +++ b/csunplugged/topics/migrations/0053_unitplan_heading_tree.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-12 02:05 +from __future__ import unicode_literals + +import django.contrib.postgres.fields.jsonb +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0052_merge_20170510_0137'), + ] + + operations = [ + migrations.AddField( + model_name='unitplan', + name='heading_tree', + field=django.contrib.postgres.fields.jsonb.JSONField(null=True), + ), + ] diff --git a/csunplugged/topics/migrations/0054_lesson_classroom_resources.py b/csunplugged/topics/migrations/0054_lesson_classroom_resources.py new file mode 100644 index 000000000..b73652f6b --- /dev/null +++ b/csunplugged/topics/migrations/0054_lesson_classroom_resources.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-12 02:51 +from __future__ import unicode_literals + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0053_unitplan_heading_tree'), + ] + + operations = [ + migrations.AddField( + model_name='lesson', + name='classroom_resources', + field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=100), null=True, size=None), + ), + ] diff --git a/csunplugged/topics/migrations/0055_programmingexerciselanguage_order.py b/csunplugged/topics/migrations/0055_programmingexerciselanguage_order.py new file mode 100644 index 000000000..4610a31ee --- /dev/null +++ b/csunplugged/topics/migrations/0055_programmingexerciselanguage_order.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-21 07:41 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0054_lesson_classroom_resources'), + ] + + operations = [ + migrations.AddField( + model_name='programmingexerciselanguage', + name='order', + field=models.PositiveSmallIntegerField(default=1), + preserve_default=False, + ), + ] diff --git a/csunplugged/topics/migrations/0056_auto_20170522_0556.py b/csunplugged/topics/migrations/0056_auto_20170522_0556.py new file mode 100644 index 000000000..346f5ab76 --- /dev/null +++ b/csunplugged/topics/migrations/0056_auto_20170522_0556.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-22 05:56 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0055_programmingexerciselanguage_order'), + ] + + operations = [ + migrations.RenameField( + model_name='programmingexerciselanguage', + old_name='order', + new_name='number', + ), + ] diff --git a/csunplugged/topics/migrations/0057_auto_20170528_0519.py b/csunplugged/topics/migrations/0057_auto_20170528_0519.py new file mode 100644 index 000000000..aec1e163f --- /dev/null +++ b/csunplugged/topics/migrations/0057_auto_20170528_0519.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-28 05:19 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0056_auto_20170522_0556'), + ] + + operations = [ + migrations.AlterModelOptions( + name='learningoutcome', + options={'ordering': ['curriculum_areas__name', 'text']}, + ), + migrations.AddField( + model_name='curriculumarea', + name='number', + field=models.PositiveSmallIntegerField(default=1), + preserve_default=False, + ), + ] diff --git a/csunplugged/topics/migrations/0057_auto_20170529_2311.py b/csunplugged/topics/migrations/0057_auto_20170529_2311.py new file mode 100644 index 000000000..66c2641f4 --- /dev/null +++ b/csunplugged/topics/migrations/0057_auto_20170529_2311.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-29 23:11 +from __future__ import unicode_literals + +import django.contrib.postgres.fields.ranges +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0056_auto_20170522_0556'), + ] + + operations = [ + migrations.CreateModel( + name='AgeRange', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('slug', models.SlugField()), + ('age_range', django.contrib.postgres.fields.ranges.IntegerRangeField()), + ('unit_plan', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='unit_plan_age_range', to='topics.UnitPlan')), + ], + ), + migrations.RemoveField( + model_name='lesson', + name='max_age', + ), + migrations.RemoveField( + model_name='lesson', + name='min_age', + ), + migrations.AddField( + model_name='lesson', + name='age_ranges', + field=models.ManyToManyField(related_name='lesson_age_range', to='topics.AgeRange'), + ), + ] diff --git a/csunplugged/topics/migrations/0058_auto_20170528_0520.py b/csunplugged/topics/migrations/0058_auto_20170528_0520.py new file mode 100644 index 000000000..f65a22333 --- /dev/null +++ b/csunplugged/topics/migrations/0058_auto_20170528_0520.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-28 05:20 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0057_auto_20170528_0519'), + ] + + operations = [ + migrations.AlterModelOptions( + name='learningoutcome', + options={'ordering': ['curriculum_areas__number', 'text']}, + ), + ] diff --git a/csunplugged/topics/migrations/0058_remove_lesson_unit_plan.py b/csunplugged/topics/migrations/0058_remove_lesson_unit_plan.py new file mode 100644 index 000000000..21a7c63f2 --- /dev/null +++ b/csunplugged/topics/migrations/0058_remove_lesson_unit_plan.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-30 00:16 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0057_auto_20170529_2311'), + ] + + operations = [ + migrations.RemoveField( + model_name='lesson', + name='unit_plan', + ), + ] diff --git a/csunplugged/topics/migrations/0059_auto_20170528_0757.py b/csunplugged/topics/migrations/0059_auto_20170528_0757.py new file mode 100644 index 000000000..a035af598 --- /dev/null +++ b/csunplugged/topics/migrations/0059_auto_20170528_0757.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-28 07:57 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0058_auto_20170528_0520'), + ] + + operations = [ + migrations.AlterModelOptions( + name='curriculumarea', + options={'ordering': ['number', 'name']}, + ), + migrations.AlterModelOptions( + name='learningoutcome', + options={'ordering': ['curriculum_areas__ordering', 'text']}, + ), + ] diff --git a/csunplugged/topics/migrations/0059_auto_20170530_0102.py b/csunplugged/topics/migrations/0059_auto_20170530_0102.py new file mode 100644 index 000000000..d840b4736 --- /dev/null +++ b/csunplugged/topics/migrations/0059_auto_20170530_0102.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-30 01:02 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0058_remove_lesson_unit_plan'), + ] + + operations = [ + migrations.RemoveField( + model_name='lesson', + name='age_ranges', + ), + migrations.AddField( + model_name='agerange', + name='lessons', + field=models.ManyToManyField(related_name='lesson_age_range', to='topics.Lesson'), + ), + ] diff --git a/csunplugged/topics/migrations/0060_auto_20170528_0809.py b/csunplugged/topics/migrations/0060_auto_20170528_0809.py new file mode 100644 index 000000000..787ef98c5 --- /dev/null +++ b/csunplugged/topics/migrations/0060_auto_20170528_0809.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-28 08:09 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0059_auto_20170528_0757'), + ] + + operations = [ + migrations.AlterModelOptions( + name='learningoutcome', + options={'ordering': ['curriculum_areas__number', 'curriculum_areas__name', 'text']}, + ), + ] diff --git a/csunplugged/topics/migrations/0060_auto_20170530_2141.py b/csunplugged/topics/migrations/0060_auto_20170530_2141.py new file mode 100644 index 000000000..3fe696d74 --- /dev/null +++ b/csunplugged/topics/migrations/0060_auto_20170530_2141.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-05-30 21:41 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0059_auto_20170530_0102'), + ] + + operations = [ + migrations.RemoveField( + model_name='agerange', + name='lessons', + ), + migrations.RemoveField( + model_name='agerange', + name='slug', + ), + migrations.RemoveField( + model_name='agerange', + name='unit_plan', + ), + migrations.AddField( + model_name='lesson', + name='age_range', + field=models.ManyToManyField(related_name='lesson_age_range', to='topics.AgeRange'), + ), + migrations.AddField( + model_name='lesson', + name='unit_plan', + field=models.ForeignKey(default='0', on_delete=django.db.models.deletion.CASCADE, related_name='unit_plan_lessons', to='topics.UnitPlan'), + preserve_default=False, + ), + ] diff --git a/csunplugged/topics/migrations/0061_merge_20170603_0408.py b/csunplugged/topics/migrations/0061_merge_20170603_0408.py new file mode 100644 index 000000000..af5829240 --- /dev/null +++ b/csunplugged/topics/migrations/0061_merge_20170603_0408.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.1 on 2017-06-03 04:08 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0060_auto_20170530_2141'), + ('topics', '0060_auto_20170528_0809'), + ] + + operations = [ + ] diff --git a/csunplugged/topics/migrations/0062_auto_20170609_0424.py b/csunplugged/topics/migrations/0062_auto_20170609_0424.py new file mode 100644 index 000000000..1f1fcf847 --- /dev/null +++ b/csunplugged/topics/migrations/0062_auto_20170609_0424.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-09 04:24 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0061_merge_20170603_0408'), + ] + + operations = [ + migrations.AlterField( + model_name='learningoutcome', + name='slug', + field=models.SlugField(max_length=80, unique=True), + ), + ] diff --git a/csunplugged/topics/migrations/0063_auto_20170610_2139.py b/csunplugged/topics/migrations/0063_auto_20170610_2139.py new file mode 100644 index 000000000..a5e89a0af --- /dev/null +++ b/csunplugged/topics/migrations/0063_auto_20170610_2139.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-10 21:39 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0062_auto_20170609_0424'), + ] + + operations = [ + migrations.CreateModel( + name='ProgrammingChallenge', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('slug', models.SlugField()), + ('name', models.CharField(max_length=200)), + ('challenge_set_number', models.PositiveSmallIntegerField()), + ('challenge_number', models.PositiveSmallIntegerField()), + ('content', models.TextField()), + ('extra_challenge', models.TextField(null=True)), + ], + ), + migrations.RenameModel( + old_name='ProgrammingExerciseDifficulty', + new_name='ProgrammingChallengeDifficulty', + ), + migrations.RenameModel( + old_name='ProgrammingExerciseLanguageImplementation', + new_name='ProgrammingChallengeImplementation', + ), + migrations.RenameModel( + old_name='ProgrammingExerciseLanguage', + new_name='ProgrammingChallengeLanguage', + ), + migrations.RenameModel( + old_name='ConnectedGeneratedResource', + new_name='ResourceDescription', + ), + migrations.RemoveField( + model_name='programmingexercise', + name='difficulty', + ), + migrations.RemoveField( + model_name='programmingexercise', + name='learning_outcomes', + ), + migrations.RemoveField( + model_name='programmingexercise', + name='topic', + ), + migrations.RenameField( + model_name='agerange', + old_name='age_range', + new_name='ages', + ), + migrations.RemoveField( + model_name='lesson', + name='programming_exercises', + ), + migrations.RemoveField( + model_name='programmingchallengeimplementation', + name='exercise', + ), + migrations.AlterField( + model_name='lesson', + name='age_range', + field=models.ManyToManyField(related_name='lessons', to='topics.AgeRange'), + ), + migrations.AlterField( + model_name='lesson', + name='generated_resources', + field=models.ManyToManyField(related_name='lessons', through='topics.ResourceDescription', to='resources.Resource'), + ), + migrations.AlterField( + model_name='lesson', + name='learning_outcomes', + field=models.ManyToManyField(related_name='lessons', to='topics.LearningOutcome'), + ), + migrations.AlterField( + model_name='lesson', + name='topic', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lessons', to='topics.Topic'), + ), + migrations.AlterField( + model_name='lesson', + name='unit_plan', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lessons', to='topics.UnitPlan'), + ), + migrations.AlterField( + model_name='unitplan', + name='topic', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='unit_plans', to='topics.Topic'), + ), + migrations.DeleteModel( + name='ProgrammingExercise', + ), + migrations.AddField( + model_name='programmingchallenge', + name='difficulty', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='programming_challenges', to='topics.ProgrammingChallengeDifficulty'), + ), + migrations.AddField( + model_name='programmingchallenge', + name='learning_outcomes', + field=models.ManyToManyField(related_name='programming_challenges', to='topics.LearningOutcome'), + ), + migrations.AddField( + model_name='programmingchallenge', + name='topic', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='programming_challenges', to='topics.Topic'), + ), + migrations.AddField( + model_name='lesson', + name='programming_challenges', + field=models.ManyToManyField(related_name='lessons', to='topics.ProgrammingChallenge'), + ), + migrations.AddField( + model_name='programmingchallengeimplementation', + name='challenge', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='implementations', to='topics.ProgrammingChallenge'), + preserve_default=False, + ), + ] diff --git a/csunplugged/topics/migrations/0064_auto_20170611_0904.py b/csunplugged/topics/migrations/0064_auto_20170611_0904.py new file mode 100644 index 000000000..6b237e7b0 --- /dev/null +++ b/csunplugged/topics/migrations/0064_auto_20170611_0904.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-11 09:04 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0063_auto_20170610_2139'), + ] + + operations = [ + migrations.AlterField( + model_name='programmingchallengeimplementation', + name='challenge', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='implementations', to='topics.ProgrammingChallenge'), + ), + ] diff --git a/csunplugged/topics/migrations/0065_lesson_computational_thinking_links.py b/csunplugged/topics/migrations/0065_lesson_computational_thinking_links.py new file mode 100644 index 000000000..b624aeedd --- /dev/null +++ b/csunplugged/topics/migrations/0065_lesson_computational_thinking_links.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-12 06:57 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0064_auto_20170611_0904'), + ] + + operations = [ + migrations.AddField( + model_name='lesson', + name='computational_thinking_links', + field=models.TextField(null=True), + ), + ] diff --git a/csunplugged/topics/migrations/0066_lesson_programming_challenges_description.py b/csunplugged/topics/migrations/0066_lesson_programming_challenges_description.py new file mode 100644 index 000000000..d1e498082 --- /dev/null +++ b/csunplugged/topics/migrations/0066_lesson_programming_challenges_description.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-12 07:53 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0065_lesson_computational_thinking_links'), + ] + + operations = [ + migrations.AddField( + model_name='lesson', + name='programming_challenges_description', + field=models.TextField(null=True), + ), + ] diff --git a/csunplugged/topics/migrations/0067_programmingchallengenumber.py b/csunplugged/topics/migrations/0067_programmingchallengenumber.py new file mode 100644 index 000000000..38912dd7f --- /dev/null +++ b/csunplugged/topics/migrations/0067_programmingchallengenumber.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-12 08:53 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0066_lesson_programming_challenges_description'), + ] + + operations = [ + migrations.CreateModel( + name='ProgrammingChallengeNumber', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('challenge_set_number', models.PositiveSmallIntegerField()), + ('challenge_number', models.PositiveSmallIntegerField()), + ('lesson', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='topics.Lesson')), + ('programming_challenge', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='topics.ProgrammingChallenge')), + ], + ), + ] diff --git a/csunplugged/topics/migrations/0068_lesson_programming_challenges_temp.py b/csunplugged/topics/migrations/0068_lesson_programming_challenges_temp.py new file mode 100644 index 000000000..55bf9afad --- /dev/null +++ b/csunplugged/topics/migrations/0068_lesson_programming_challenges_temp.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-12 19:26 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0067_programmingchallengenumber'), + ] + + operations = [ + migrations.AddField( + model_name='lesson', + name='programming_challenges_temp', + field=models.ManyToManyField(related_name='lessons_temp', through='topics.ProgrammingChallengeNumber', to='topics.ProgrammingChallenge'), + ), + ] diff --git a/csunplugged/topics/migrations/0069_remove_lesson_programming_challenges.py b/csunplugged/topics/migrations/0069_remove_lesson_programming_challenges.py new file mode 100644 index 000000000..a50728281 --- /dev/null +++ b/csunplugged/topics/migrations/0069_remove_lesson_programming_challenges.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-12 19:27 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0068_lesson_programming_challenges_temp'), + ] + + operations = [ + migrations.RemoveField( + model_name='lesson', + name='programming_challenges', + ), + ] diff --git a/csunplugged/topics/migrations/0070_auto_20170612_1927.py b/csunplugged/topics/migrations/0070_auto_20170612_1927.py new file mode 100644 index 000000000..52879b03f --- /dev/null +++ b/csunplugged/topics/migrations/0070_auto_20170612_1927.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-12 19:27 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0069_remove_lesson_programming_challenges'), + ] + + operations = [ + migrations.RemoveField( + model_name='lesson', + name='programming_challenges_temp', + ), + migrations.AddField( + model_name='lesson', + name='programming_challenges', + field=models.ManyToManyField(related_name='lessons', through='topics.ProgrammingChallengeNumber', to='topics.ProgrammingChallenge'), + ), + ] diff --git a/csunplugged/topics/migrations/0071_auto_20170612_2349.py b/csunplugged/topics/migrations/0071_auto_20170612_2349.py new file mode 100644 index 000000000..5177e88d3 --- /dev/null +++ b/csunplugged/topics/migrations/0071_auto_20170612_2349.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-12 23:49 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0070_auto_20170612_1927'), + ] + + operations = [ + migrations.CreateModel( + name='LessonNumber', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('number', models.PositiveSmallIntegerField()), + ('age_range', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='topics.AgeRange')), + ], + ), + migrations.RenameField( + model_name='lesson', + old_name='number', + new_name='sorting_number', + ), + migrations.AddField( + model_name='lessonnumber', + name='lesson', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='topics.Lesson'), + ), + migrations.AddField( + model_name='lesson', + name='age_range_temp', + field=models.ManyToManyField(related_name='lessons_temp', through='topics.LessonNumber', to='topics.AgeRange'), + ), + ] diff --git a/csunplugged/topics/migrations/0072_remove_lesson_age_range.py b/csunplugged/topics/migrations/0072_remove_lesson_age_range.py new file mode 100644 index 000000000..7c5795ad3 --- /dev/null +++ b/csunplugged/topics/migrations/0072_remove_lesson_age_range.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-12 23:52 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0071_auto_20170612_2349'), + ] + + operations = [ + migrations.RemoveField( + model_name='lesson', + name='age_range', + ), + ] diff --git a/csunplugged/topics/migrations/0073_auto_20170612_2352.py b/csunplugged/topics/migrations/0073_auto_20170612_2352.py new file mode 100644 index 000000000..5c49f6584 --- /dev/null +++ b/csunplugged/topics/migrations/0073_auto_20170612_2352.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-12 23:52 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0072_remove_lesson_age_range'), + ] + + operations = [ + migrations.RemoveField( + model_name='lesson', + name='age_range_temp', + ), + migrations.AddField( + model_name='lesson', + name='age_range', + field=models.ManyToManyField(related_name='lessons', through='topics.LessonNumber', to='topics.AgeRange'), + ), + ] diff --git a/csunplugged/topics/migrations/0074_auto_20170613_0156.py b/csunplugged/topics/migrations/0074_auto_20170613_0156.py new file mode 100644 index 000000000..47b8fd307 --- /dev/null +++ b/csunplugged/topics/migrations/0074_auto_20170613_0156.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-13 01:56 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0073_auto_20170612_2352'), + ] + + operations = [ + migrations.AlterModelOptions( + name='agerange', + options={'ordering': ['ages']}, + ), + ] diff --git a/csunplugged/topics/migrations/0075_auto_20170613_0201.py b/csunplugged/topics/migrations/0075_auto_20170613_0201.py new file mode 100644 index 000000000..3fc880d39 --- /dev/null +++ b/csunplugged/topics/migrations/0075_auto_20170613_0201.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-13 02:01 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0074_auto_20170613_0156'), + ] + + operations = [ + migrations.AlterModelOptions( + name='lessonnumber', + options={'ordering': ['number']}, + ), + ] diff --git a/csunplugged/topics/migrations/0076_remove_lesson_sorting_number.py b/csunplugged/topics/migrations/0076_remove_lesson_sorting_number.py new file mode 100644 index 000000000..f4fc5459d --- /dev/null +++ b/csunplugged/topics/migrations/0076_remove_lesson_sorting_number.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-15 05:44 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0075_auto_20170613_0201'), + ] + + operations = [ + migrations.RemoveField( + model_name='lesson', + name='sorting_number', + ), + ] diff --git a/csunplugged/topics/migrations/0077_agerange_description.py b/csunplugged/topics/migrations/0077_agerange_description.py new file mode 100644 index 000000000..11fddc3d8 --- /dev/null +++ b/csunplugged/topics/migrations/0077_agerange_description.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-16 07:07 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0076_remove_lesson_sorting_number'), + ] + + operations = [ + migrations.AddField( + model_name='agerange', + name='description', + field=models.CharField(max_length=500, null=True), + ), + ] diff --git a/csunplugged/topics/migrations/0078_agerange_slug.py b/csunplugged/topics/migrations/0078_agerange_slug.py new file mode 100644 index 000000000..8f78427e8 --- /dev/null +++ b/csunplugged/topics/migrations/0078_agerange_slug.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-16 07:20 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0077_agerange_description'), + ] + + operations = [ + migrations.AddField( + model_name='agerange', + name='slug', + field=models.SlugField(default='Unset slug'), + preserve_default=False, + ), + ] diff --git a/csunplugged/topics/migrations/0079_auto_20170616_0855.py b/csunplugged/topics/migrations/0079_auto_20170616_0855.py new file mode 100644 index 000000000..ff434b405 --- /dev/null +++ b/csunplugged/topics/migrations/0079_auto_20170616_0855.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-16 08:55 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0078_agerange_slug'), + ] + + operations = [ + migrations.RenameModel( + old_name='AgeRange', + new_name='AgeGroup', + ), + migrations.RenameField( + model_name='lesson', + old_name='age_range', + new_name='age_group', + ), + migrations.RenameField( + model_name='lessonnumber', + old_name='age_range', + new_name='age_group', + ), + ] diff --git a/csunplugged/topics/migrations/0080_auto_20170618_0124.py b/csunplugged/topics/migrations/0080_auto_20170618_0124.py new file mode 100644 index 000000000..ae14d1b39 --- /dev/null +++ b/csunplugged/topics/migrations/0080_auto_20170618_0124.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-18 01:24 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0079_auto_20170616_0855'), + ] + + operations = [ + migrations.AlterField( + model_name='lesson', + name='slug', + field=models.SlugField(max_length=100), + ), + ] diff --git a/csunplugged/topics/migrations/0081_unitplan_computational_thinking_links.py b/csunplugged/topics/migrations/0081_unitplan_computational_thinking_links.py new file mode 100644 index 000000000..122559ae9 --- /dev/null +++ b/csunplugged/topics/migrations/0081_unitplan_computational_thinking_links.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.2 on 2017-06-19 03:50 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('topics', '0080_auto_20170618_0124'), + ] + + operations = [ + migrations.AddField( + model_name='unitplan', + name='computational_thinking_links', + field=models.TextField(null=True), + ), + ] diff --git a/csunplugged/topics/migrations/__init__.py b/csunplugged/topics/migrations/__init__.py new file mode 100644 index 000000000..e94e6008e --- /dev/null +++ b/csunplugged/topics/migrations/__init__.py @@ -0,0 +1 @@ +"""Module for migrations for the topics application.""" diff --git a/csunplugged/topics/models.py b/csunplugged/topics/models.py new file mode 100644 index 000000000..9a30e0eab --- /dev/null +++ b/csunplugged/topics/models.py @@ -0,0 +1,412 @@ +"""Models for the topics application.""" + +from django.db import models +from django.contrib.postgres.fields import ArrayField, JSONField, IntegerRangeField +from resources.models import Resource + + +class GlossaryTerm(models.Model): + """Model for glossary term in database.""" + + # Auto-incrementing 'id' field is automatically set by Django + slug = models.SlugField(unique=True) + term = models.CharField(max_length=200, unique=True, null=True) + definition = models.TextField() + + def __str__(self): + """Text representation of GlossaryTerm object. + + Returns: + Term attribute of GlossaryTerm (str). + """ + return self.term + + +class CurriculumArea(models.Model): + """Model for curriculum area in database.""" + + # Auto-incrementing 'id' field is automatically set by Django + slug = models.SlugField(unique=True) + name = models.CharField(max_length=100, unique=True) + number = models.PositiveSmallIntegerField() + colour = models.CharField(max_length=15, null=True) + parent = models.ForeignKey( + "self", + null=True, + related_name="parent_curriculum_area" + ) + + def __str__(self): + """Text representation of CurriculumArea object. + + Returns: + Name of curriculum area (str). + """ + if self.parent: + return "{}: {}".format(self.parent.name, self.name) + else: + return self.name + + class Meta: + """Set consistent ordering of curriculum areas.""" + + ordering = ["number", "name"] + + +class LearningOutcome(models.Model): + """Model for learning outcome in database.""" + + # Auto-incrementing 'id' field is automatically set by Django + slug = models.SlugField(max_length=80, unique=True) + text = models.CharField(max_length=200, unique=True) + curriculum_areas = models.ManyToManyField( + CurriculumArea, + related_name='learning_outcomes', + ) + + def __str__(self): + """Text representation of LearningOutcome object. + + Returns: + Text of learning outcome (str). + """ + return self.text + + class Meta: + """Set consistent ordering of learning outcomes.""" + + ordering = ["curriculum_areas__number", "curriculum_areas__name", "text"] + + +class Topic(models.Model): + """Model for topic in database.""" + + # Auto-incrementing 'id' field is automatically set by Django + slug = models.SlugField(unique=True) + name = models.CharField(max_length=100) + content = models.TextField() + other_resources = models.TextField(null=True) + icon = models.CharField(max_length=100, null=True) + + def __str__(self): + """Text representation of Topic object. + + Returns: + Name of topic (str). + """ + return self.name + + +class UnitPlan(models.Model): + """Model for unit plan in database.""" + + # Auto-incrementing 'id' field is automatically set by Django + topic = models.ForeignKey( + Topic, + on_delete=models.CASCADE, + related_name="unit_plans" + ) + slug = models.SlugField() + name = models.CharField(max_length=100) + content = models.TextField() + computational_thinking_links = models.TextField(null=True) + heading_tree = JSONField(null=True) + + def __str__(self): + """Text representation of UnitPlan object. + + Returns: + Name of unit plan (str). + """ + return self.name + + +class ProgrammingChallengeDifficulty(models.Model): + """Model for programming challenge difficulty in database.""" + + # Auto-incrementing 'id' field is automatically set by Django + level = models.PositiveSmallIntegerField(unique=True) + name = models.CharField(max_length=100, unique=True) + + def __str__(self): + """Text representation of ProgrammingChallengeDifficulty object. + + Returns: + Name of difficulty level (str). + """ + return self.name + + +class ProgrammingChallenge(models.Model): + """Model for programming challenge in database.""" + + # Auto-incrementing 'id' field is automatically set by Django + topic = models.ForeignKey( + Topic, + on_delete=models.CASCADE, + related_name="programming_challenges" + ) + slug = models.SlugField() + name = models.CharField(max_length=200) + challenge_set_number = models.PositiveSmallIntegerField() + challenge_number = models.PositiveSmallIntegerField() + content = models.TextField() + extra_challenge = models.TextField(null=True) + learning_outcomes = models.ManyToManyField( + LearningOutcome, + related_name="programming_challenges" + ) + difficulty = models.ForeignKey( + ProgrammingChallengeDifficulty, + on_delete=models.CASCADE, + related_name="programming_challenges" + ) + + def ordered_implementations(self): + """Return an ordered QuerySet of implementations. + + Returns: + Ordered QuerySet. + """ + return self.implementations.all().order_by("language__number").select_related() + + def __str__(self): + """Text representation of ProgrammingChallenge object. + + Returns: + Name of programming challenge (str). + """ + return self.name + + +class ProgrammingChallengeLanguage(models.Model): + """Model for programming language in database.""" + + # Auto-incrementing 'id' field is automatically set by Django + slug = models.SlugField() + name = models.CharField(max_length=200) + number = models.PositiveSmallIntegerField() + icon = models.CharField(max_length=100, null=True) + + def __str__(self): + """Text representation of ProgrammingChallengeLanguage object. + + Returns: + Name of programming language (str). + """ + return self.name + + +class ProgrammingChallengeImplementation(models.Model): + """Model for programming challenge language implementation in database.""" + + # Auto-incrementing 'id' field is automatically set by Django + topic = models.ForeignKey( + Topic, + on_delete=models.CASCADE, + related_name="implementations" + ) + language = models.ForeignKey( + ProgrammingChallengeLanguage, + on_delete=models.CASCADE, + related_name="implementations" + ) + challenge = models.ForeignKey( + ProgrammingChallenge, + on_delete=models.CASCADE, + related_name="implementations" + ) + expected_result = models.TextField() + hints = models.TextField(null=True) + solution = models.TextField() + + def __str__(self): + """Text representation of ProgrammingChallengeImplementation. + + Returns: + Description of implementation and related challenge (str). + """ + return "{} for challenge {}.{}, {}".format( + self.language.name, + self.challenge.challenge_set_number, + self.challenge.challenge_number, + self.challenge.name + ) + + +class AgeGroup(models.Model): + """Model for age group in database.""" + + # Auto-incrementing 'id' field is automatically set by Django + slug = models.SlugField() + ages = IntegerRangeField() + description = models.CharField(max_length=500, null=True) + + def __str__(self): + """Text representation of AgeGroup object. + + Returns: + Integer group (str). + """ + return repr(self.ages) + + class Meta: + """Set consistent ordering of age groups.""" + + ordering = ["ages"] + + +class Lesson(models.Model): + """Model for lesson in database.""" + + # Auto-incrementing 'id' field is automatically set by Django + topic = models.ForeignKey( + Topic, + on_delete=models.CASCADE, + related_name="lessons" + ) + unit_plan = models.ForeignKey( + UnitPlan, + on_delete=models.CASCADE, + related_name="lessons" + ) + slug = models.SlugField(max_length=100) + name = models.CharField(max_length=100) + duration = models.PositiveSmallIntegerField(null=True) + content = models.TextField() + computational_thinking_links = models.TextField(null=True) + heading_tree = JSONField(null=True) + age_group = models.ManyToManyField( + AgeGroup, + through="LessonNumber", + related_name="lessons" + ) + programming_challenges = models.ManyToManyField( + ProgrammingChallenge, + through="ProgrammingChallengeNumber", + related_name="lessons" + ) + programming_challenges_description = models.TextField(null=True) + learning_outcomes = models.ManyToManyField( + LearningOutcome, + related_name="lessons" + ) + generated_resources = models.ManyToManyField( + Resource, + through="ResourceDescription", + related_name="lessons" + ) + classroom_resources = ArrayField( + models.CharField(max_length=100), + null=True + ) + + def has_programming_challenges(self): + """Return boolean of lesson having any programming challenges. + + Returns: + True if the lesson has connected programming challenges. + Otherwise False. + """ + return bool(self.programming_challenges.all()) + + def retrieve_related_programming_challenges(self): + """Retrieve the lesson's programming challenges and update numbers. + + Returns: + QuerySet of programming challenges with updated numbers. + """ + programming_challenges = self.programming_challenges.order_by( + "challenge_set_number", + "challenge_number", + "name", + ) + for programming_challenge in programming_challenges: + challenge_numbers = ProgrammingChallengeNumber.objects.get( + lesson=self, + programming_challenge=programming_challenge + ) + programming_challenge.challenge_set_number = challenge_numbers.challenge_set_number + programming_challenge.challenge_number = challenge_numbers.challenge_number + return programming_challenges + + def __str__(self): + """Text representation of Lesson object. + + Returns: + Name of lesson (str). + """ + return self.name + + +class LessonNumber(models.Model): + """Model for relationship between age group and lesson in database.""" + + # Auto-incrementing 'id' field is automatically set by Django + age_group = models.ForeignKey(AgeGroup, on_delete=models.CASCADE) + lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE) + number = models.PositiveSmallIntegerField() + + class Meta: + """Set consistent ordering of age groups.""" + + ordering = ["number"] + + +class ProgrammingChallengeNumber(models.Model): + """Model for relationship between programming challenge and lesson in database.""" + + # Auto-incrementing 'id' field is automatically set by Django + programming_challenge = models.ForeignKey(ProgrammingChallenge, on_delete=models.CASCADE) + lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE) + challenge_set_number = models.PositiveSmallIntegerField() + challenge_number = models.PositiveSmallIntegerField() + + +class CurriculumIntegration(models.Model): + """Model for curriculum integration in database.""" + + # Auto-incrementing 'id' field is automatically set by Django + topic = models.ForeignKey( + Topic, + on_delete=models.CASCADE, + related_name="curriculum_integrations" + ) + slug = models.SlugField() + number = models.PositiveSmallIntegerField() + name = models.CharField(max_length=200) + content = models.TextField() + curriculum_areas = models.ManyToManyField( + CurriculumArea, + related_name="curriculum_integrations", + ) + prerequisite_lessons = models.ManyToManyField( + Lesson, + related_name="curriculum_integrations" + ) + + def has_prerequisite_lessons(self): + """Return boolean of integration having any prerequisite lessons. + + Returns: + True if the curriculum integration has at + least one prerequisite lesson, otherwise False. + """ + return bool(self.prerequisite_lessons.all()) + + def __str__(self): + """Text representation of CurriculumIntegration object. + + Returns: + Name of curriculum integration (str). + """ + return self.name + + +class ResourceDescription(models.Model): + """Model for relationship between resource and lesson in database.""" + + # Auto-incrementing 'id' field is automatically set by Django + resource = models.ForeignKey(Resource, on_delete=models.CASCADE) + lesson = models.ForeignKey(Lesson, on_delete=models.CASCADE) + description = models.CharField(max_length=300) diff --git a/csunplugged/topics/urls.py b/csunplugged/topics/urls.py new file mode 100644 index 000000000..8d82643f6 --- /dev/null +++ b/csunplugged/topics/urls.py @@ -0,0 +1,81 @@ +"""URL routing for the topics application.""" + +from django.conf.urls import url + +from . import views + +app_name = "topics" +urlpatterns = [ + # eg: /topics/ + url( + r"^$", + views.IndexView.as_view(), + name="index" + ), + # eg: /topics/glossary/ + url( + r"^glossary/$", + views.GlossaryList.as_view(), + name="glossary" + ), + # eg: /topics/glossary/json/ + url( + r"^glossary/json/$", + views.glossary_json, + name="glossary_json" + ), + # eg: /topics/curriculum-integrations/ + url( + r"^curriculum-integrations/$", + views.AllCurriculumIntegrationList.as_view(), + name="all_curriculum_integrations" + ), + # eg: /topics/binary-numbers/ + url( + r"^(?P[-\w]+)/$", + views.TopicView.as_view(), + name="topic" + ), + # eg: /topics/binary-numbers/integrations/binary-bracelets/ + url( + r"^(?P[-\w]+)/integrations/(?P[-\w]+)/$", + views.CurriculumIntegrationView.as_view(), + name="integration" + ), + # eg: /topics/binary-numbers/other-resources/ + url( + r"^(?P[-\w]+)/other-resources/$", + views.OtherResourcesView.as_view(), + name="other_resources" + ), + # eg: /topics/binary-numbers/unit-plan/ + url( + r"^(?P[-\w]+)/(?P[-\w]+)/$", + views.UnitPlanView.as_view(), + name="unit_plan" + ), + # eg: /topics/binary-numbers/unit-plan/lesson-1/ + url( + r"^(?P[-\w]+)/(?P[-\w]+)/(?P[-\w]+)/$", + views.LessonView.as_view(), + name="lesson" + ), + # eg: /topics/binary-numbers/unit-plan/lesson-1/programming/ + url( + r"^(?P[-\w]+)/(?P[-\w]+)/(?P[-\w]+)/programming/$", + views.ProgrammingChallengeList.as_view(), + name="programming_challenges_list" + ), + # eg: /topics/binary-numbers/programming/challenge-1/ + url( + r"^(?P[-\w]+)/programming/(?P[-\w]+)$", # noqa: E501 + views.ProgrammingChallengeView.as_view(), + name="programming_challenge" + ), + # eg: /topics/binary-numbers/programming/challenge-1/python-solution/ + url( + r"^(?P[-\w]+)/programming/(?P[-\w]+)/(?P[-\w]+)-solution$", # noqa: E501 + views.ProgrammingChallengeLanguageSolutionView.as_view(), + name="programming_challenge_solution" + ), +] diff --git a/csunplugged/topics/views.py b/csunplugged/topics/views.py new file mode 100644 index 000000000..1272507ea --- /dev/null +++ b/csunplugged/topics/views.py @@ -0,0 +1,374 @@ +"""Views for the topics application.""" + +from django.shortcuts import get_object_or_404 +from django.views import generic +from django.http import JsonResponse, Http404 +from config.templatetags.render_html_field import render_html_with_static +from utils.group_lessons_by_age import group_lessons_by_age +from .models import ( + Topic, + CurriculumIntegration, + UnitPlan, + Lesson, + LessonNumber, + ProgrammingChallenge, + ProgrammingChallengeNumber, + ProgrammingChallengeImplementation, + ResourceDescription, + GlossaryTerm, +) + + +class IndexView(generic.ListView): + """View for the topics application homepage.""" + + template_name = "topics/index.html" + context_object_name = "all_topics" + + def get_queryset(self): + """Get queryset of all topics. + + Returns: + Queryset of Topic objects ordered by name. + """ + return Topic.objects.order_by("name") + + +class TopicView(generic.DetailView): + """View for a specific topic.""" + + model = Topic + template_name = "topics/topic.html" + slug_url_kwarg = "topic_slug" + + def get_context_data(self, **kwargs): + """Provide the context data for the topic view. + + Returns: + Dictionary of context data. + """ + # Call the base implementation first to get a context + context = super(TopicView, self).get_context_data(**kwargs) + # Add in a QuerySet of all the connected unit plans + unit_plans = UnitPlan.objects.filter(topic=self.object).order_by("name").select_related() + for unit_plan in unit_plans: + unit_plan.grouped_lessons = group_lessons_by_age(unit_plan.lessons.all()) + context["unit_plans"] = unit_plans + # Add in a QuerySet of all the connected curriculum integrations + context["curriculum_integrations"] = CurriculumIntegration.objects.filter(topic=self.object).order_by("number") + context["programming_challenges"] = ProgrammingChallenge.objects.filter(topic=self.object).order_by( + "challenge_set_number", + "challenge_number" + ) + lessons = self.object.lessons.all() + resources = set() + for lesson in lessons: + lesson_resources = lesson.generated_resources.all() + for lesson_resource in lesson_resources: + resources.add(lesson_resource) + context["resources"] = resources + return context + + +class UnitPlanView(generic.DetailView): + """View for a specific unit plan.""" + + model = UnitPlan + template_name = "topics/unit-plan.html" + context_object_name = "unit_plan" + + def get_object(self, **kwargs): + """Retrieve object for the unit plan view. + + Returns: + UnitPlan object, or raises 404 error if not found. + """ + return get_object_or_404( + self.model.objects.select_related(), + topic__slug=self.kwargs.get("topic_slug", None), + slug=self.kwargs.get("unit_plan_slug", None) + ) + + def get_context_data(self, **kwargs): + """Provide the context data for the unit plan view. + + Returns: + Dictionary of context data. + """ + # Call the base implementation first to get a context + context = super(UnitPlanView, self).get_context_data(**kwargs) + # Loading object under consistent context names for breadcrumbs + context["topic"] = self.object.topic + # Add all the connected lessons + context["grouped_lessons"] = group_lessons_by_age(self.object.lessons.all()) + return context + + +class LessonView(generic.DetailView): + """View for a specific lesson.""" + + model = Lesson + template_name = "topics/lesson.html" + context_object_name = "lesson" + + def get_object(self, **kwargs): + """Retrieve object for the lesson view. + + Returns: + Lesson object, or raises 404 error if not found. + """ + return get_object_or_404( + self.model.objects.select_related(), + topic__slug=self.kwargs.get("topic_slug", None), + unit_plan__slug=self.kwargs.get("unit_plan_slug", None), + slug=self.kwargs.get("lesson_slug", None), + ) + + def get_context_data(self, **kwargs): + """Provide the context data for the lesson view. + + Returns: + Dictionary of context data. + """ + # Call the base implementation first to get a context + context = super(LessonView, self).get_context_data(**kwargs) + # Loading objects under consistent context names for breadcrumbs + context["lesson_ages"] = [] + for age_group in self.object.age_group.order_by("ages"): + number = LessonNumber.objects.get(lesson=self.object, age_group=age_group).number + context["lesson_ages"].append( + { + "lower": age_group.ages.lower, + "upper": age_group.ages.upper, + "number": number, + } + ) + context["topic"] = self.object.topic + context["unit_plan"] = self.object.unit_plan + # Add all the connected programming challenges + context["programming_challenges"] = self.object.programming_challenges.all() + # Add all the connected learning outcomes + context["learning_outcomes"] = self.object.learning_outcomes.all().select_related() + # Add all the connected generated resources + related_resources = self.object.generated_resources.all() + generated_resources = [] + for related_resource in related_resources: + generated_resource = dict() + generated_resource["slug"] = related_resource.slug + generated_resource["name"] = related_resource.name + generated_resource["thumbnail"] = related_resource.thumbnail_static_path + relationship = ResourceDescription.objects.get(resource=related_resource, lesson=self.object) + generated_resource["description"] = relationship.description + generated_resources.append(generated_resource) + context["generated_resources"] = generated_resources + + return context + + +class ProgrammingChallengeList(generic.base.TemplateView): + """View for listing all programming challenges for a lesson.""" + + template_name = "topics/programming-challenge-lesson-list.html" + + def get_context_data(self, **kwargs): + """Provide the context data for the programming challenge list view. + + Returns: + Dictionary of context data. + """ + context = super(ProgrammingChallengeList, self).get_context_data(**kwargs) + lesson = get_object_or_404( + Lesson.objects.select_related(), + topic__slug=self.kwargs.get("topic_slug", None), + unit_plan__slug=self.kwargs.get("unit_plan_slug", None), + slug=self.kwargs.get("lesson_slug", None), + ) + context["lesson"] = lesson + context["programming_challenges"] = lesson.retrieve_related_programming_challenges() + context["unit_plan"] = lesson.unit_plan + context["topic"] = lesson.topic + return context + + +class ProgrammingChallengeView(generic.DetailView): + """View for a specific programming challenge.""" + + model = ProgrammingChallenge + template_name = "topics/programming-challenge.html" + context_object_name = "programming_challenge" + + def get_object(self, **kwargs): + """Retrieve object for the programming challenge view. + + Returns: + ProgrammingChallenge object, or raises 404 error if not found. + """ + return get_object_or_404( + self.model.objects.select_related(), + topic__slug=self.kwargs.get("topic_slug", None), + slug=self.kwargs.get("programming_challenge_slug", None) + ) + + def get_context_data(self, **kwargs): + """Provide the context data for the programming challenge view. + + Returns: + Dictionary of context data. + """ + # Call the base implementation first to get a context + context = super(ProgrammingChallengeView, self).get_context_data(**kwargs) + context["lessons"] = self.object.lessons.all() + for lesson in context["lessons"]: + challenge_numbers = ProgrammingChallengeNumber.objects.get( + lesson=lesson, + programming_challenge=self.object + ) + lesson.challenge_set_number = challenge_numbers.challenge_set_number + lesson.challenge_number = challenge_numbers.challenge_number + context["topic"] = self.object.topic + # Add all the connected learning outcomes + context["learning_outcomes"] = self.object.learning_outcomes.all() + context["implementations"] = self.object.ordered_implementations() + return context + + +class ProgrammingChallengeLanguageSolutionView(generic.DetailView): + """View for a language implementation for a programming challenge.""" + + model = ProgrammingChallengeImplementation + template_name = "topics/programming-challenge-language-solution.html" + context_object_name = "implementation" + + def get_object(self, **kwargs): + """Retrieve object for the language implementation view. + + Returns: + ProgrammingChallengeImplementation object, or raises 404 + error if not found. + """ + return get_object_or_404( + self.model.objects.select_related(), + topic__slug=self.kwargs.get("topic_slug", None), + challenge__slug=self.kwargs.get("programming_challenge_slug", None), + language__slug=self.kwargs.get("programming_language_slug", None) + ) + + def get_context_data(self, **kwargs): + """Provide the context data for the language implementation view. + + Returns: + Dictionary of context data. + """ + # Call the base implementation first to get a context + context = super(ProgrammingChallengeLanguageSolutionView, self).get_context_data(**kwargs) + # Loading object under consistent context names for breadcrumbs + context["topic"] = self.object.topic + context["programming_challenge"] = self.object.challenge + return context + + +class AllCurriculumIntegrationList(generic.ListView): + """View for listing all curriculum integrations.""" + + model = CurriculumIntegration + template_name = "topics/all-curriculum-integration-list.html" + context_object_name = "curriculum_integrations" + + def get_queryset(self, **kwargs): + """Retrieve all curriculum integrations. + + Returns: + Queryset of CurriculumIntegration objects. + """ + return CurriculumIntegration.objects.select_related().order_by("topic__name", "number") + + +class CurriculumIntegrationView(generic.DetailView): + """View for a specific curriculum integration.""" + + model = CurriculumIntegration + queryset = CurriculumIntegration.objects.all() + template_name = "topics/curriculum-integration.html" + context_object_name = "integration" + + def get_object(self, **kwargs): + """Retrieve object for the curriculum integration view. + + Returns: + CurriculumIntegration object, or raises 404 error if not found. + """ + return get_object_or_404( + self.model.objects.select_related(), + topic__slug=self.kwargs.get("topic_slug", None), + slug=self.kwargs.get("integration_slug", None) + ) + + def get_context_data(self, **kwargs): + """Provide the context data for the curriculum integration view. + + Returns: + Dictionary of context data. + """ + # Call the base implementation first to get a context + context = super(CurriculumIntegrationView, self).get_context_data(**kwargs) + # Loading objects under consistent context names for breadcrumbs + context["topic"] = self.object.topic + # Add in a QuerySet of all the connected curriculum areas + context["integration_curriculum_areas"] = self.object.curriculum_areas.order_by("name") + # Add in a QuerySet of all the prerequisite lessons + context["prerequisite_lessons"] = self.object.prerequisite_lessons.select_related().order_by( + "unit_plan__name", + ) + return context + + +class OtherResourcesView(generic.DetailView): + """View for detailing other resources for a specific topic.""" + + model = Topic + template_name = "topics/topic-other-resources.html" + slug_url_kwarg = "topic_slug" + + +class GlossaryList(generic.ListView): + """Provide glossary view of all terms.""" + + template_name = "topics/glossary.html" + context_object_name = "glossary_terms" + + def get_queryset(self): + """Get queryset of all glossary terms. + + Returns: + Queryset of GlossaryTerm objects ordered by term. + """ + return GlossaryTerm.objects.order_by("term") + + +def glossary_json(request, **kwargs): + """Provide JSON data for glossary term. + + Args: + request: The HTTP request. + + Returns: + JSON response is sent containing data for the requested term. + + Raises: + 404 error if term not found. + """ + # If term parameter, then return JSON + if "term" in request.GET: + glossary_slug = request.GET.get("term") + glossary_item = get_object_or_404( + GlossaryTerm, + slug=glossary_slug + ) + data = { + "slug": glossary_slug, + "term": glossary_item.term, + "definition": render_html_with_static(glossary_item.definition) + } + return JsonResponse(data) + else: + raise Http404("Term parameter not specified.") diff --git a/csunplugged/utils/BaseLoader.py b/csunplugged/utils/BaseLoader.py new file mode 100644 index 000000000..8f7056d3e --- /dev/null +++ b/csunplugged/utils/BaseLoader.py @@ -0,0 +1,161 @@ +"""Base loader used to create custom loaders for content.""" + +import yaml +import mdx_math +import abc +import sys +import re +import os.path +from os import listdir +from verto import Verto + +from .check_required_files import check_converter_required_files +from .check_glossary_links import check_converter_glossary_links +from utils.errors.CouldNotFindMarkdownFileError import CouldNotFindMarkdownFileError +from utils.errors.EmptyMarkdownFileError import EmptyMarkdownFileError +from utils.errors.EmptyConfigFileError import EmptyConfigFileError +from utils.errors.InvalidConfigFileError import InvalidConfigFileError +from utils.errors.NoHeadingFoundInMarkdownFileError import NoHeadingFoundInMarkdownFileError +from utils.errors.CouldNotFindConfigFileError import CouldNotFindConfigFileError + + +class BaseLoader(): + """Base loader class for individual loaders.""" + + def __init__(self, BASE_PATH=""): + """Create a BaseLoader object. + + Args: + BASE_PATH: string of base path (str). + """ + self.BASE_PATH = BASE_PATH + self.setup_md_to_html_converter() + + def setup_md_to_html_converter(self): + """Create Markdown converter. + + The converter is created with custom processors, html templates, + and extensions. + """ + templates = self.load_template_files() + extensions = [ + "markdown.extensions.fenced_code", + "markdown.extensions.codehilite", + "markdown.extensions.sane_lists", + "markdown.extensions.tables", + mdx_math.MathExtension() + ] + self.converter = Verto(html_templates=templates, extensions=extensions) + + def convert_md_file(self, md_file_path, config_file_path, heading_required=True, remove_title=True): + """Return the Verto object for a given Markdown file. + + Args: + md_file_path: Location of Markdown file to convert (str). + config_file_path: Path to related the config file (str). + heading_required: Boolean if the file requires a heading (bool). + remove_title: Boolean if the file's first heading should be removed (bool). + + Returns: + VertoResult object + + Raises: + CouldNotFindMarkdownFileError: when a given Markdown file cannot be found. + NoHeadingFoundInMarkdownFileError: when no heading can be found in a given + Markdown file. + EmptyMarkdownFileError: when no content can be found in a given Markdown + file. + """ + try: + # check file exists + content = open(md_file_path, encoding="UTF-8").read() + except: + raise CouldNotFindMarkdownFileError(md_file_path, config_file_path) + + custom_processors = self.converter.processor_defaults() + if remove_title: + custom_processors.add("remove-title") + self.converter.update_processors(custom_processors) + + result = self.converter.convert(content) + + if heading_required: + if result.title is None: + raise NoHeadingFoundInMarkdownFileError(md_file_path) + + if len(result.html_string) == 0: + raise EmptyMarkdownFileError(md_file_path) + check_converter_required_files(result.required_files, md_file_path) + check_converter_glossary_links(result.required_glossary_terms, md_file_path) + return result + + def log(self, message, indent_amount=0): + """Output the log message to the load log. + + Args: + message: Text to display (str). + indent_amount: Amount of indentation required (int). + """ + indent = " " * indent_amount + text = "{indent}{text}\n".format(indent=indent, text=message) + sys.stdout.write(text) + + def load_yaml_file(self, yaml_file_path): + """Load and read given YAML file. + + Args: + file_path: location of yaml file to read (str). + + Returns: + Either list or string, depending on structure of given yaml file + + Raises: + CouldNotFindConfigFileError: when a given config file cannot be found. + InvalidConfigFileError: when a given config file is incorrectly formatted. + EmptyConfigFileError: when a give config file is empty. + """ + try: + yaml_file = open(yaml_file_path, encoding="UTF-8").read() + except: + raise CouldNotFindConfigFileError(yaml_file_path) + + try: + yaml_contents = yaml.load(yaml_file) + except: + raise InvalidConfigFileError(yaml_file_path) + + if yaml_contents is None: + raise EmptyConfigFileError(yaml_file_path) + + if isinstance(yaml_contents, dict) is False: + raise InvalidConfigFileError(yaml_file_path) + + return yaml_contents + + def load_template_files(self): + """Load custom HTML templates for converter. + + Returns: + templates: dictionary of html templates + """ + templates = dict() + template_path = os.path.join( + os.path.dirname(__file__), + "custom_converter_templates/" + ) + for file in listdir(template_path): + template_file = re.search(r"(.*?).html$", file) + if template_file: + template_name = template_file.groups()[0] + templates[template_name] = open(template_path + file).read() + return templates + + @abc.abstractmethod + def load(self): + """Abstract method to be implemented by subclasses. + + Raise: + NotImplementedError: when a user attempts to run the load() method of the + BaseLoader class. + """ + raise NotImplementedError("Subclass does not implement this method") diff --git a/csunplugged/utils/LoaderFactory.py b/csunplugged/utils/LoaderFactory.py new file mode 100644 index 000000000..b8b56802c --- /dev/null +++ b/csunplugged/utils/LoaderFactory.py @@ -0,0 +1,61 @@ +"""Factory for creating loader objects.""" + +from topics.management.commands._AgeGroupsLoader import AgeGroupsLoader +from topics.management.commands._CurriculumAreasLoader import CurriculumAreasLoader +from topics.management.commands._CurriculumIntegrationsLoader import CurriculumIntegrationsLoader +from topics.management.commands._GlossaryTermsLoader import GlossaryTermsLoader +from topics.management.commands._LearningOutcomesLoader import LearningOutcomesLoader +from topics.management.commands._LessonsLoader import LessonsLoader +from topics.management.commands._ProgrammingChallengesLoader import ProgrammingChallengesLoader +from topics.management.commands._ProgrammingChallengesStructureLoader import ProgrammingChallengesStructureLoader +from topics.management.commands._TopicLoader import TopicLoader +from topics.management.commands._UnitPlanLoader import UnitPlanLoader +from resources.management.commands._ResourcesLoader import ResourcesLoader + + +class LoaderFactory: + """Factory for creating loader objects.""" + + def create_age_groups_loader(self, structure_file_path, BASE_PATH): + """Create age group loader.""" + return AgeGroupsLoader(structure_file_path, BASE_PATH) + + def create_curriculum_areas_loader(self, structure_file_path, BASE_PATH): + """Create curriculum area loader.""" + return CurriculumAreasLoader(structure_file_path, BASE_PATH) + + def create_curriculum_integrations_loader(self, structure_file_path, topic, BASE_PATH): + """Create curriculum integrations loader.""" + return CurriculumIntegrationsLoader(structure_file_path, topic, BASE_PATH) + + def create_glossary_terms_loader(self, glossary_folder_path, structure_file_path, BASE_PATH): + """Create glossary terms loader.""" + return GlossaryTermsLoader(glossary_folder_path, structure_file_path, BASE_PATH) + + def create_learning_outcomes_loader(self, structure_file_path, BASE_PATH): + """Create learning outcomes loader.""" + return LearningOutcomesLoader(structure_file_path, BASE_PATH) + + def create_lessons_loader(self, structure_file_path, topic, unit_plan, BASE_PATH): + """Create lessons loader.""" + return LessonsLoader(structure_file_path, topic, unit_plan, BASE_PATH) + + def create_programming_challenges_loader(self, structure_file, topic, BASE_PATH): + """Create programming challenges loader.""" + return ProgrammingChallengesLoader(structure_file, topic, BASE_PATH) + + def create_programming_challenges_structure_loader(self, structure_file_path, BASE_PATH): + """Create programming challenges structure loader.""" + return ProgrammingChallengesStructureLoader(structure_file_path, BASE_PATH) + + def create_topic_loader(self, structure_file_path, BASE_PATH): + """Create topic loader.""" + return TopicLoader(self, structure_file_path, BASE_PATH) + + def create_unit_plan_loader(self, structure_file_path, topic, BASE_PATH): + """Create unit plan loader.""" + return UnitPlanLoader(self, structure_file_path, topic, BASE_PATH) + + def create_resources_loader(self, structure_file, BASE_PATH): + """Create resources loader.""" + return ResourcesLoader(structure_file, BASE_PATH) diff --git a/csunplugged/utils/check_glossary_links.py b/csunplugged/utils/check_glossary_links.py new file mode 100644 index 000000000..c33a247f4 --- /dev/null +++ b/csunplugged/utils/check_glossary_links.py @@ -0,0 +1,18 @@ +"""Module for checking glossary links found within Markdown conversions.""" + +from django.core.exceptions import ObjectDoesNotExist +from utils.errors.CouldNotFindGlossaryTerm import CouldNotFindGlossaryTerm +from topics.models import GlossaryTerm + + +def check_converter_glossary_links(glossary_links, md_file_path): + """Process glossary links found by Markdown converter. + + Args: + glossary_links: Dictionary of glossary links (dict). + """ + for slug in glossary_links.keys(): + try: + GlossaryTerm.objects.get(slug=slug) + except ObjectDoesNotExist: + raise CouldNotFindGlossaryTerm(slug, md_file_path) diff --git a/csunplugged/utils/check_required_files.py b/csunplugged/utils/check_required_files.py new file mode 100644 index 000000000..43ebd544d --- /dev/null +++ b/csunplugged/utils/check_required_files.py @@ -0,0 +1,48 @@ +"""Module for checking required files found within Markdown conversions.""" + +import os +import os.path +from django.contrib.staticfiles import finders +from utils.errors.CouldNotFindImageError import CouldNotFindImageError + + +def check_converter_required_files(required_files, md_file_path): + """Process data within required files found by Markdown converter. + + Args: + required_files: Dictionary of required files data (dict). + """ + render_scratch_images(required_files["scratch_images"]) + find_image_files(required_files["images"], md_file_path) + + +def render_scratch_images(scratch_images): + """Write Scratch data for image rendering by Gulp script. + + Args: + scratch_images: List of named tuples containing + scratch image data to be rendered (list). + """ + FILEPATH_TEMPLATE = "temp/scratch-blocks-{hash}.txt" + if scratch_images and not os.path.exists("temp"): + os.makedirs("temp") + for scratch_image in scratch_images: + filepath = FILEPATH_TEMPLATE.format(hash=scratch_image.hash) + if not os.path.isfile(filepath): + with open(filepath, "w") as scratch_temp_file: + scratch_temp_file.write(scratch_image.text) + + +def find_image_files(images, md_file_path): + """Confirm each image is in static folder. + + Args: + images: image file names (set). + md_file_path: path to Markdown file (str). + + Raises: + CouldNotFindImageError: when image file cannot be found. + """ + for image in images: + if not finders.find(image): + raise CouldNotFindImageError(image, md_file_path) diff --git a/csunplugged/utils/convert_heading_tree_to_dict.py b/csunplugged/utils/convert_heading_tree_to_dict.py new file mode 100644 index 000000000..dbee8f626 --- /dev/null +++ b/csunplugged/utils/convert_heading_tree_to_dict.py @@ -0,0 +1,39 @@ +"""Module used for converting Verto heading tree data. + +The Verto result object is a tuple containing NamedTuples, however this +must be converted to a dictionary to be stored in a HStoreField Postgres +database field. +""" + + +def convert_heading_tree_to_dict(heading_tree_tuples): + """Convert tuple heading tree to dictionary. + + Args: + heading_tree_tuples: The heading tree from a Verto conversion. + + Returns: + Dictionary of heading tree, or None if None provided as tree. + """ + return [convert_heading_node_to_dict(heading_node) for heading_node in heading_tree_tuples] + + +def convert_heading_node_to_dict(heading_node): + """Convert a heading node to a dictionary. + + Args: + heading_node: A tuple for a heading node. + + Returns: + A dictionary of data for a heading node. + """ + children = [] + for child in heading_node.children: + children.append(convert_heading_node_to_dict(child)) + heading_node_data = { + "text": heading_node.title, + "slug": heading_node.title_slug, + "level": heading_node.level, + "children": children, + } + return heading_node_data diff --git a/csunplugged/utils/create_query_string.py b/csunplugged/utils/create_query_string.py new file mode 100644 index 000000000..c91f09fbd --- /dev/null +++ b/csunplugged/utils/create_query_string.py @@ -0,0 +1,18 @@ +"""Module for creating GET query string from dictionary.""" + + +def query_string(values): + """Create a GET query to append to a URL from the given values. + + Args: + values: A dictionary of keys/values of GET parameters. + + Returns: + String of GET query. + """ + string = "?" + for index, (key, value) in enumerate(values.items()): + string += "{key}={value}".format(key=key, value=value) + if index < len(values): + string += "&" + return string diff --git a/csunplugged/utils/custom_converter_templates/boxed-text.html b/csunplugged/utils/custom_converter_templates/boxed-text.html new file mode 100644 index 000000000..9bdd24e8c --- /dev/null +++ b/csunplugged/utils/custom_converter_templates/boxed-text.html @@ -0,0 +1,3 @@ +
+
{{ text }}
+
diff --git a/csunplugged/utils/custom_converter_templates/button-link.html b/csunplugged/utils/custom_converter_templates/button-link.html new file mode 100644 index 000000000..8cd178278 --- /dev/null +++ b/csunplugged/utils/custom_converter_templates/button-link.html @@ -0,0 +1 @@ +{{ text }} diff --git a/csunplugged/utils/custom_converter_templates/heading.html b/csunplugged/utils/custom_converter_templates/heading.html new file mode 100644 index 000000000..b60b93dc5 --- /dev/null +++ b/csunplugged/utils/custom_converter_templates/heading.html @@ -0,0 +1 @@ +<{{ heading_type }} class="heading-underline anchor" id="{{ title_slug }}">{{ title }} diff --git a/csunplugged/utils/custom_converter_templates/iframe.html b/csunplugged/utils/custom_converter_templates/iframe.html new file mode 100644 index 000000000..98ae47642 --- /dev/null +++ b/csunplugged/utils/custom_converter_templates/iframe.html @@ -0,0 +1,8 @@ +
+
+ +
+

+ View full screen +

+
diff --git a/csunplugged/utils/custom_converter_templates/image-inline.html b/csunplugged/utils/custom_converter_templates/image-inline.html new file mode 100644 index 000000000..bc4edd8f5 --- /dev/null +++ b/csunplugged/utils/custom_converter_templates/image-inline.html @@ -0,0 +1,22 @@ + + + {% if caption %} +

+ {% if caption_link %} + + {% endif %} + {{ caption }} + {% if caption_link %} + + {% endif %} +

+ {% endif %} + {% if source_link %} +

+ Source +

+ {% endif %} +
diff --git a/csunplugged/utils/custom_converter_templates/image.html b/csunplugged/utils/custom_converter_templates/image.html new file mode 100644 index 000000000..5bd981479 --- /dev/null +++ b/csunplugged/utils/custom_converter_templates/image.html @@ -0,0 +1,22 @@ +
+ +{% if caption %} +
+{% if caption_link %} + +{% endif %} +{{ caption }} +{% if caption_link %} + +{% endif %} +
+{% endif %} +{% if source_link %} +
+Source +
+{% endif %} +
diff --git a/csunplugged/utils/custom_converter_templates/panel.html b/csunplugged/utils/custom_converter_templates/panel.html new file mode 100644 index 000000000..b03bf9a31 --- /dev/null +++ b/csunplugged/utils/custom_converter_templates/panel.html @@ -0,0 +1,16 @@ + + +{{ title }}{% if subtitle %}: {{ subtitle }}{% else %}{% endif %} +{% if (type == 'ct-algorithm') or +(type == 'ct-abstraction') or +(type == 'ct-decomposition') or +(type == 'ct-pattern') or +(type == 'ct-logic') or +(type == 'ct-evaluation') %} + +{% endif %} + +
+{% autoescape false %}{{ content }}{% endautoescape %} +
+ diff --git a/csunplugged/utils/custom_converter_templates/scratch-inline.html b/csunplugged/utils/custom_converter_templates/scratch-inline.html new file mode 100644 index 000000000..574681498 --- /dev/null +++ b/csunplugged/utils/custom_converter_templates/scratch-inline.html @@ -0,0 +1 @@ + diff --git a/csunplugged/utils/custom_converter_templates/scratch.html b/csunplugged/utils/custom_converter_templates/scratch.html new file mode 100644 index 000000000..807361d89 --- /dev/null +++ b/csunplugged/utils/custom_converter_templates/scratch.html @@ -0,0 +1,5 @@ +
+ {% for hash in images -%} + + {% endfor -%} + diff --git a/csunplugged/utils/custom_converter_templates/video.html b/csunplugged/utils/custom_converter_templates/video.html new file mode 100644 index 000000000..fa5f18988 --- /dev/null +++ b/csunplugged/utils/custom_converter_templates/video.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/csunplugged/utils/errors/CouldNotFindConfigFileError.py b/csunplugged/utils/errors/CouldNotFindConfigFileError.py new file mode 100644 index 000000000..850bc8ebd --- /dev/null +++ b/csunplugged/utils/errors/CouldNotFindConfigFileError.py @@ -0,0 +1,24 @@ +"""Custom error for missing config file.""" + +from .Error import Error + +ERROR_MESSAGE = "\nCould not find config file.\n" + + +class CouldNotFindConfigFileError(Error): + """Custom error for missing config file.""" + + def __init__(self, config_file_path): + """Create the error for missing config file.""" + super().__init__() + self.config_file_path = config_file_path + + def __str__(self): + """ + Override default error string. + + Returns: + Error message for missing config file. + """ + base_message = self.base_message.format(filename=self.config_file_path) + return base_message + ERROR_MESSAGE + self.missing_file_suggestions diff --git a/csunplugged/utils/errors/CouldNotFindGlossaryTerm.py b/csunplugged/utils/errors/CouldNotFindGlossaryTerm.py new file mode 100644 index 000000000..6b914eee4 --- /dev/null +++ b/csunplugged/utils/errors/CouldNotFindGlossaryTerm.py @@ -0,0 +1,35 @@ +"""Custom error for missing glossary term.""" + +from .Error import Error + +ERROR_MESSAGE_TEMPLATE = """ +Could not find glossary term: {term} + +Options: + - Is the glossary term key defined in the + application structure file? + - Is the term spelt correctly? +""" + + +class CouldNotFindGlossaryTerm(Error): + """Custom error for missing glossary term.""" + + def __init__(self, term, reference_file_path): + """Create the error could not find glossary term.""" + super().__init__() + self.term = term + self.reference_file_path = reference_file_path + + def __str__(self): + """ + Override default error string. + + Returns: + Error message for missing glossary term. + """ + base_message = self.base_message.format(filename=self.reference_file_path) + missing_field_message = ERROR_MESSAGE_TEMPLATE.format( + term=self.term + ) + return base_message + missing_field_message diff --git a/csunplugged/utils/errors/CouldNotFindImageError.py b/csunplugged/utils/errors/CouldNotFindImageError.py new file mode 100644 index 000000000..443546d28 --- /dev/null +++ b/csunplugged/utils/errors/CouldNotFindImageError.py @@ -0,0 +1,26 @@ +"""Custom error for missing image.""" + +from .Error import Error + +ERROR_MESSAGE = "\nCould not find image.\n" + + +class CouldNotFindImageError(Error): + """Custom error for missing image.""" + + def __init__(self, image_path, reference_file_path): + """Create the error missing image.""" + super().__init__() + self.image_path = image_path + self.reference_file_path = reference_file_path + + def __str__(self): + """ + Override default error string. + + Returns: + Error message for missing image file. + """ + base_message = self.base_message.format(filename=self.image_path) + reference = self.reference_message.format(reference=self.reference_file_path) + return base_message + reference + ERROR_MESSAGE + self.missing_file_suggestions diff --git a/csunplugged/utils/errors/CouldNotFindMarkdownFileError.py b/csunplugged/utils/errors/CouldNotFindMarkdownFileError.py new file mode 100644 index 000000000..aea6c7170 --- /dev/null +++ b/csunplugged/utils/errors/CouldNotFindMarkdownFileError.py @@ -0,0 +1,26 @@ +"""Custom error for missing markdown file.""" + +from .Error import Error + +ERROR_MESSAGE = "\nCould not find Markdown file.\n" + + +class CouldNotFindMarkdownFileError(Error): + """Custom error for missing markdown file.""" + + def __init__(self, md_file_path, config_file_path): + """Create the error for missing markdown file.""" + super().__init__() + self.md_file_path = md_file_path + self.config_file_path = config_file_path + + def __str__(self): + """ + Override default error string. + + Returns: + Error message for missing markdown file. + """ + base_message = self.base_message.format(filename=self.md_file_path) + reference = self.reference_message.format(reference=self.config_file_path) + return base_message + reference + ERROR_MESSAGE + self.missing_file_suggestions diff --git a/csunplugged/utils/errors/EmptyConfigFileError.py b/csunplugged/utils/errors/EmptyConfigFileError.py new file mode 100644 index 000000000..5c97bc430 --- /dev/null +++ b/csunplugged/utils/errors/EmptyConfigFileError.py @@ -0,0 +1,22 @@ +"""Custom error for empty config file.""" + +from .Error import Error + +ERROR_MESSAGE = "\nA config file cannot be empty.\n" + + +class EmptyConfigFileError(Error): + """Custom error for empty config file.""" + + def __init__(self, yaml_file_path): + """Create the error for empty config file.""" + super().__init__() + self.yaml_file_path = yaml_file_path + + def __str__(self): + """Override default error string. + + Returns: + Error message for empty config file. + """ + return self.base_message.format(filename=self.yaml_file_path) + ERROR_MESSAGE diff --git a/csunplugged/utils/errors/EmptyMarkdownFileError.py b/csunplugged/utils/errors/EmptyMarkdownFileError.py new file mode 100644 index 000000000..f91732cba --- /dev/null +++ b/csunplugged/utils/errors/EmptyMarkdownFileError.py @@ -0,0 +1,27 @@ +"""Custom error for empty markdown file.""" + +from .Error import Error + +ERROR_MESSAGE = """ +The file contains no content. + +Note: A file containing a title and no other content is +also considered to be empty. +""" + + +class EmptyMarkdownFileError(Error): + """Custom error for empty markdown file.""" + + def __init__(self, md_file_path): + """Create the error for empty markdown file.""" + super().__init__() + self.md_file_path = md_file_path + + def __str__(self): + """Override default error string. + + Returns: + Error message for empty markdown file. + """ + return self.base_message.format(filename=self.md_file_path) + ERROR_MESSAGE diff --git a/csunplugged/utils/errors/Error.py b/csunplugged/utils/errors/Error.py new file mode 100644 index 000000000..a37389151 --- /dev/null +++ b/csunplugged/utils/errors/Error.py @@ -0,0 +1,23 @@ +"""Base error used to create custom errors for loaders.""" + +ERROR_TITLE_TEMPLATE = "\n****************************ERROR****************************\n" +ERROR_FILENAME_TEMPLATE = "File: {filename}\n" +ERROR_REFERENCE_TEMPLATE = "Referenced in: {reference}\n" +ERROR_SUGGESTIONS_TEMPLATE = """ + - Did you spell the name of the file correctly? + - Does the file exist? + - Is the file saved in the correct directory? +""" + + +class Error(Exception): + """Base class for Errors. + + (Exceptions from external sources such as inputs). + """ + + def __init__(self): + """Create the base class for errors.""" + self.base_message = ERROR_TITLE_TEMPLATE + ERROR_FILENAME_TEMPLATE + self.reference_message = ERROR_REFERENCE_TEMPLATE + self.missing_file_suggestions = ERROR_SUGGESTIONS_TEMPLATE diff --git a/csunplugged/utils/errors/InvalidConfigFileError.py b/csunplugged/utils/errors/InvalidConfigFileError.py new file mode 100644 index 000000000..20312f1ca --- /dev/null +++ b/csunplugged/utils/errors/InvalidConfigFileError.py @@ -0,0 +1,29 @@ +"""Custom error for invalid config file.""" + +from .Error import Error + +ERROR_MESSAGE = """ +Invalid configuration file. + +Options: + - Does the file match the expected layout? + - Does the file contain at least one key:value pair? + - Is the syntax correct? (are you missing a colon somewhere?) +""" + + +class InvalidConfigFileError(Error): + """custom error for invalid config file.""" + + def __init__(self, yaml_file_path): + """Create error for invalid config file.""" + super().__init__() + self.yaml_file_path = yaml_file_path + + def __str__(self): + """Override default error string. + + Returns: + Error message for invalid config file. + """ + return self.base_message.format(filename=self.yaml_file_path) + ERROR_MESSAGE diff --git a/csunplugged/utils/errors/InvalidConfigValueError.py b/csunplugged/utils/errors/InvalidConfigValueError.py new file mode 100644 index 000000000..dd118fbaf --- /dev/null +++ b/csunplugged/utils/errors/InvalidConfigValueError.py @@ -0,0 +1,30 @@ +"""Custom error for invalid config file value.""" + +from .Error import Error + +ERROR_MESSAGE = """ +Invalid configuration file value for: {key} + +Expected: {expected} +""" + + +class InvalidConfigValueError(Error): + """Custom error for invalid config file value.""" + + def __init__(self, yaml_file_path, key, expected): + """Create error for invalid config file value.""" + super().__init__() + self.yaml_file_path = yaml_file_path + self.key = key + self.expected = expected + + def __str__(self): + """Override default error string. + + Returns: + Error message for invalid config file value. + """ + base_message = self.base_message.format(filename=self.yaml_file_path) + error_message = ERROR_MESSAGE.format(key=self.key, expected=self.expected) + return base_message + error_message diff --git a/csunplugged/utils/errors/KeyNotFoundError.py b/csunplugged/utils/errors/KeyNotFoundError.py new file mode 100644 index 000000000..2c9efac52 --- /dev/null +++ b/csunplugged/utils/errors/KeyNotFoundError.py @@ -0,0 +1,33 @@ +"""Custom error for unknown key.""" + +from .Error import Error + +ERROR_MESSAGE_TEMPLATE = """ +Key: {key} +"{key}" did not match any {field} + +Options: + - Did you spell the name of the key correctly? + - Does the key exist? +""" + + +class KeyNotFoundError(Error): + """Custom error for unknown key.""" + + def __init__(self, config_file_path, key, field): + """Create the error for unknown key.""" + super().__init__() + self.config_file_path = config_file_path + self.key = key + self.field = field + + def __str__(self): + """Override default error string. + + Returns: + Error message for unknown key. + """ + base_message = self.base_message.format(filename=self.config_file_path) + error_message = ERROR_MESSAGE_TEMPLATE.format(key=self.key, field=self.field) + return base_message + error_message diff --git a/csunplugged/utils/errors/MissingRequiredFieldError.py b/csunplugged/utils/errors/MissingRequiredFieldError.py new file mode 100644 index 000000000..d1a9abf40 --- /dev/null +++ b/csunplugged/utils/errors/MissingRequiredFieldError.py @@ -0,0 +1,42 @@ +"""Custom error for missing required field.""" + +from .Error import Error + +ERROR_MESSAGE_TEMPLATE = """ +A {model} requires the following field{plural}: +{fields} +For the missing field{plural}: + - Is the field name spelt correctly? + - Does the field have the correct value? +""" + + +class MissingRequiredFieldError(Error): + """Custom error for missing required field.""" + + def __init__(self, config_file_path, required_fields, model): + """Create error for missing required field.""" + super().__init__() + self.config_file_path = config_file_path + self.required_fields = required_fields + self.model = model + + def __str__(self): + """Override default error string. + + Returns: + Error message for missing required field. + """ + fields = "" + for field in self.required_fields: + fields += " - {field}\n".format(field=str(field)) + if len(self.required_fields) > 1: + plural = "s" + else: + plural = "" + missing_field_message = ERROR_MESSAGE_TEMPLATE.format( + model=self.model, + fields=fields, + plural=plural, + ) + return self.base_message.format(filename=self.config_file_path) + missing_field_message diff --git a/csunplugged/utils/errors/NoHeadingFoundInMarkdownFileError.py b/csunplugged/utils/errors/NoHeadingFoundInMarkdownFileError.py new file mode 100644 index 000000000..8f2ef9f10 --- /dev/null +++ b/csunplugged/utils/errors/NoHeadingFoundInMarkdownFileError.py @@ -0,0 +1,22 @@ +"""Custom error for missing heading.""" + +from .Error import Error + +ERROR_MESSAGE = "\nThe file does not contain a heading.\n" + + +class NoHeadingFoundInMarkdownFileError(Error): + """Custom error for missing heading.""" + + def __init__(self, md_file_path): + """Create error for missing heading.""" + super().__init__() + self.md_file_path = md_file_path + + def __str__(self): + """Override default error string. + + Returns: + Error message for missing heading. + """ + return self.base_message.format(filename=self.md_file_path) + ERROR_MESSAGE diff --git a/csunplugged/utils/errors/__init__.py b/csunplugged/utils/errors/__init__.py new file mode 100644 index 000000000..5c5271fee --- /dev/null +++ b/csunplugged/utils/errors/__init__.py @@ -0,0 +1 @@ +"""Module for errors raised in loaders.""" diff --git a/csunplugged/utils/group_lessons_by_age.py b/csunplugged/utils/group_lessons_by_age.py new file mode 100644 index 000000000..5e63f860f --- /dev/null +++ b/csunplugged/utils/group_lessons_by_age.py @@ -0,0 +1,33 @@ +"""Return ordered groups of lessons.""" + +from collections import OrderedDict +from topics.models import ( + AgeGroup, + LessonNumber, +) + + +def group_lessons_by_age(lessons): + """Return ordered groups of lessons. + + Lessons are grouped by the lesson minimum age and maximum ages, + and then order by number. + + Args: + lessons: QuerySet of Lesson objects (QuerySet). + + Returns: + A ordered dictionary of grouped lessons. + The key is a tuple of the minimum age and maximum ages for + the lessons. + The value for a key is a sorted list of lessons (ordered by number). + """ + grouped_lessons = OrderedDict() + for age_group in AgeGroup.objects.distinct(): + for lesson in age_group.lessons.filter(id__in=lessons).order_by("lessonnumber"): + lesson.number = LessonNumber.objects.get(lesson=lesson, age_group=age_group).number + if age_group in grouped_lessons.keys(): + grouped_lessons[age_group].append(lesson) + else: + grouped_lessons[age_group] = [lesson] + return grouped_lessons diff --git a/csunplugged/utils/import_resource_module.py b/csunplugged/utils/import_resource_module.py new file mode 100644 index 000000000..49c227b36 --- /dev/null +++ b/csunplugged/utils/import_resource_module.py @@ -0,0 +1,19 @@ +"""Module for importing a resource view module.""" + +import importlib + + +def import_resource_module(resource): + """Return view module for resource. + + Args: + resource: Resource model object. + + Returns: + Python module for resource. + """ + resource_view = resource.generation_view + if resource_view.endswith(".py"): + resource_view = resource_view[:-3] + module_path = "resources.views.{}".format(resource_view) + return importlib.import_module(module_path) diff --git a/csunplugged/utils/resource_valid_test_configurations.py b/csunplugged/utils/resource_valid_test_configurations.py new file mode 100644 index 000000000..4a2febaee --- /dev/null +++ b/csunplugged/utils/resource_valid_test_configurations.py @@ -0,0 +1,20 @@ +"""Create list of all possible valid resource combinations.""" + +import itertools + + +def resource_valid_test_configurations(valid_options): + """Return list of all possible valid resource combinations. + + Args: + valid_options: A dictionary containing all valid resource generation + options (dict). + + Returns: + List of lists of valid combinations (list). + """ + valid_options["header_text"] = ["", "Example header"] + valid_option_keys = sorted(valid_options) + return [dict(zip(valid_option_keys, product)) for product in itertools.product( + *(valid_options[valid_option_key] for valid_option_key in valid_option_keys) + )] diff --git a/csunplugged/utils/retrieve_query_parameter.py b/csunplugged/utils/retrieve_query_parameter.py new file mode 100644 index 000000000..150764054 --- /dev/null +++ b/csunplugged/utils/retrieve_query_parameter.py @@ -0,0 +1,29 @@ +"""Module for retrieving a GET request query parameter.""" + +from django.http import Http404 + + +def retrieve_query_parameter(request, parameter, valid_options=None): + """Retrieve the query parameter. + + If the parameter cannot be found, or is not found in the list of + valid options, then a 404 error is raised. + + Args: + request: Request object (Request). + parameter: Parameter to retrieve (str). + valid_options: If provided, a list of valid options (list of str). + + Returns: + String value of parameter. + """ + value = request.get(parameter, None) + if value is None: + raise Http404("{} parameter not specified.".format(parameter)) + if value == "yes": + value = True + elif value == "no": + value = False + if valid_options and value not in valid_options: + raise Http404("{} parameter not valid.".format(parameter)) + return value diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..f2a4b0573 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,32 @@ +version: '3' +services: + postgres: + restart: always + image: postgres + + django: + build: + context: . + dockerfile: ./Dockerfile-local + command: /cs-unplugged/csunplugged/docker-development-entrypoint.sh + volumes: + - .:/cs-unplugged + environment: + - PORT=8080 + - USE_DOCKER=yes + - DATABASE_URL=postgres://postgres@postgres:5432/postgres + - DJANGO_SETTINGS_MODULE=config.settings.local + depends_on: + - postgres + + nginx: + build: + context: . + dockerfile: ./infrastructure/nginx/Dockerfile + volumes: + - ./csunplugged/:/app/ + - /app/node_modules + depends_on: + - django + ports: + - "80:80" diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000..809d9a3b2 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = CSUnplugged +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 000000000..e312960d4 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,36 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build +set SPHINXPROJ=CSUnplugged + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% + +:end +popd diff --git a/docs/source/_static/css/custom-styles.css b/docs/source/_static/css/custom-styles.css new file mode 100644 index 000000000..5f75ce586 --- /dev/null +++ b/docs/source/_static/css/custom-styles.css @@ -0,0 +1,39 @@ +div#tree-diagram { + font-size: 14px; + line-height: 1.2; + color: #404040; + background: #fff; + border: solid 1px #e1e4e5; + padding: 0.5em; + margin: 1em 0; + font-family: Consolas,"Andale Mono WT","Andale Mono","Lucida Console","Lucida Sans Typewriter","DejaVu Sans Mono","Bitstream Vera Sans Mono","Liberation Mono","Nimbus Mono L",Monaco,"Courier New",Courier,monospace; +} + +div#tree-diagram span.directory { + color: #2980B9; +} + +div#tree-diagram span.file { + color: #E74C3C; +} + +div#tree-diagram span.text-file { + color: #3FB322; +} + +table.markdown-example { + margin: 1em 0; +} + +table.markdown-example thead { + background-color: #DEE0E2; +} + +table.markdown-example th, +table.markdown-example td { + padding: 6px 13px; + border: 1px solid #CFD1D4; +} +table.markdown-example tr:nth-child(2n) { + background-color: #F1F1F1; +} diff --git a/docs/source/_static/html_snippets/markdown_example_table.html b/docs/source/_static/html_snippets/markdown_example_table.html new file mode 100644 index 000000000..c16de64ce --- /dev/null +++ b/docs/source/_static/html_snippets/markdown_example_table.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +
TablesAreCool
col 3 isright-aligned$1600
col 2 iscentered$12
zebra stripesare neat$1
diff --git a/docs/source/_static/html_snippets/markdown_example_table_2.html b/docs/source/_static/html_snippets/markdown_example_table_2.html new file mode 100644 index 000000000..6aa2bb59f --- /dev/null +++ b/docs/source/_static/html_snippets/markdown_example_table_2.html @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + +
MarkdownLessPretty
Stillrendersnicely
123
diff --git a/docs/source/_static/html_snippets/project_directory_tree.html b/docs/source/_static/html_snippets/project_directory_tree.html new file mode 100644 index 000000000..e368f7572 --- /dev/null +++ b/docs/source/_static/html_snippets/project_directory_tree.html @@ -0,0 +1,21 @@ +
+ ├── csunplugged/
+ │   ├── build/
+ │   ├── config/
+ │   ├── general/
+ │   ├── gulpfile.js
+ │   ├── locale/
+ │   ├── manage.py
+ │   ├── package.json
+ │   ├── resources/
+ │   ├── static/
+ │   ├── temp/
+ │   ├── templates/
+ │   ├── topics/
+ │   └── utils/
+ ├── docs/
+ ├── LICENCE.md
+ ├── README.md
+ ├── requirements/
+ └── subtitles/
+
diff --git a/docs/source/_static/html_snippets/topics_content_directory_tree.html b/docs/source/_static/html_snippets/topics_content_directory_tree.html new file mode 100644 index 000000000..6b70cb351 --- /dev/null +++ b/docs/source/_static/html_snippets/topics_content_directory_tree.html @@ -0,0 +1,52 @@ +
+ ├── binary-numbers
+ │   ├── curriculum-integrations
+ │   │   ├── curriculum-integrations.yaml
+ │   │   └── bracelets.md
+ │   ├── programming-challenges
+ │   │   ├── count-to-16
+ │   │   │   ├── count-to-16.md
+ │   │   │   ├── python-expected.md
+ │   │   │   ├── python-hints.md
+ │   │   │   ├── python-solution.md
+ │   │   │   ├── scratch-expected.md
+ │   │   │   ├── scratch-hints.md
+ │   │   │   └── scratch-solution.md
+ │   │   ├── count-to-a-hundred
+ │   │   │   ├── count-to-a-hundred.md
+ │   │   │   ├── scratch-expected.md
+ │   │   │   ├── scratch-hints.md
+ │   │   │   └── scratch-solution.md
+ │   │   └── programming-challenges.yaml
+ │   ├── unit-plan
+ │   │    ├── lessons
+ │   │    │   ├── 5-8
+ │   │    │   │    ├── introduction-to-bits.md
+ │   │    │   │    └── bytes.md
+ │   │    │   └── 9-12
+ │   │    │        └── counting-in-binary.md
+ │   │    ├── unit-plan.yaml
+ │   │    └── unit-plan.md
+ │   ├── binary-numbers.yaml
+ │   ├── binary-numbers.md
+ │   └── other-resources.md
+ ├── error-detection-correction
+ │   ├── curriculum-integrations
+ │   │   ├── curriculum-integrations.yaml
+ │   │   └── painting-parity.md
+ │   ├── unit-plan
+ │   │    ├── lessons
+ │   │    │   └── 5-8
+ │   │    │        └── introduction.md
+ │   │    ├── unit-plan.yaml
+ │   │    └── unit-plan.md
+ │   ├── error-detection-correction.yaml
+ │   └── error-detection-correction.md
+ ├── glossary
+ │   ├── algorithm.md
+ │   └── pixel.md
+ ├── curriculum-areas.yaml
+ ├── learning-outcomes.yaml
+ ├── programming-challenges-structure.yaml
+ └── structure.yaml
+
diff --git a/docs/source/_static/html_snippets/topics_content_directory_tree_only_yaml.html b/docs/source/_static/html_snippets/topics_content_directory_tree_only_yaml.html new file mode 100644 index 000000000..b4e3fbb4e --- /dev/null +++ b/docs/source/_static/html_snippets/topics_content_directory_tree_only_yaml.html @@ -0,0 +1,23 @@ +
+ ├── binary-numbers
+ │   ├── binary-numbers.yaml
+ │   ├── curriculum-integrations
+ │   │   └── curriculum-integrations.yaml
+ │   ├── programming-challenges
+ │   │   └── programming-challenges.yaml
+ │   └── unit-plan
+ │       ├── unit-plan.yaml
+ │       └── lessons
+ │           └── lessons.yaml
+ ├── curriculum-areas.yaml
+ ├── error-detection-correction
+ │   ├── error-detection-correction.yaml
+ │   ├── curriculum-integrations
+ │   │   └── curriculum-integrations.yaml
+ │   └── unit-plan
+ │       └── unit-plan.yaml
+ ├── glossary
+ ├── learning-outcomes.yaml
+ ├── programming-challenges-structure.yaml
+ └── structure.yaml
+
diff --git a/docs/source/_static/img/database_overview_diagram.png b/docs/source/_static/img/database_overview_diagram.png new file mode 100644 index 000000000..1a0dae5aa Binary files /dev/null and b/docs/source/_static/img/database_overview_diagram.png differ diff --git a/docs/source/_static/img/markdown_example_rendered.png b/docs/source/_static/img/markdown_example_rendered.png new file mode 100644 index 000000000..be2861975 Binary files /dev/null and b/docs/source/_static/img/markdown_example_rendered.png differ diff --git a/docs/source/_static/img/scratch_block_types.png b/docs/source/_static/img/scratch_block_types.png new file mode 100644 index 000000000..1f81bd61c Binary files /dev/null and b/docs/source/_static/img/scratch_block_types.png differ diff --git a/docs/source/_static/img/scratch_multiple_join_blocks.png b/docs/source/_static/img/scratch_multiple_join_blocks.png new file mode 100644 index 000000000..09cafc2f6 Binary files /dev/null and b/docs/source/_static/img/scratch_multiple_join_blocks.png differ diff --git a/docs/source/_static/img/scratch_say_block.png b/docs/source/_static/img/scratch_say_block.png new file mode 100644 index 000000000..ab187b7e8 Binary files /dev/null and b/docs/source/_static/img/scratch_say_block.png differ diff --git a/docs/source/_static/img/scratch_set_block.png b/docs/source/_static/img/scratch_set_block.png new file mode 100644 index 000000000..798d846a5 Binary files /dev/null and b/docs/source/_static/img/scratch_set_block.png differ diff --git a/docs/source/_static/img/topics_adding_curriculum_areas_flowchart.png b/docs/source/_static/img/topics_adding_curriculum_areas_flowchart.png new file mode 100644 index 000000000..d9942e92d Binary files /dev/null and b/docs/source/_static/img/topics_adding_curriculum_areas_flowchart.png differ diff --git a/docs/source/_static/img/topics_adding_curriculum_integrations_flowchart.png b/docs/source/_static/img/topics_adding_curriculum_integrations_flowchart.png new file mode 100644 index 000000000..7b52148a2 Binary files /dev/null and b/docs/source/_static/img/topics_adding_curriculum_integrations_flowchart.png differ diff --git a/docs/source/_static/img/topics_adding_glossary_definitions_flowchart.png b/docs/source/_static/img/topics_adding_glossary_definitions_flowchart.png new file mode 100644 index 000000000..cfea37104 Binary files /dev/null and b/docs/source/_static/img/topics_adding_glossary_definitions_flowchart.png differ diff --git a/docs/source/_static/img/topics_adding_learning_outcomes_flowchart.png b/docs/source/_static/img/topics_adding_learning_outcomes_flowchart.png new file mode 100644 index 000000000..1f52333a3 Binary files /dev/null and b/docs/source/_static/img/topics_adding_learning_outcomes_flowchart.png differ diff --git a/docs/source/_static/img/topics_adding_lesson_flowchart.png b/docs/source/_static/img/topics_adding_lesson_flowchart.png new file mode 100644 index 000000000..0add96c09 Binary files /dev/null and b/docs/source/_static/img/topics_adding_lesson_flowchart.png differ diff --git a/docs/source/_static/img/topics_adding_programming_challenges_flowchart.png b/docs/source/_static/img/topics_adding_programming_challenges_flowchart.png new file mode 100644 index 000000000..cb59baff6 Binary files /dev/null and b/docs/source/_static/img/topics_adding_programming_challenges_flowchart.png differ diff --git a/docs/source/_static/img/topics_adding_topic_flowchart.png b/docs/source/_static/img/topics_adding_topic_flowchart.png new file mode 100644 index 000000000..7c54e554c Binary files /dev/null and b/docs/source/_static/img/topics_adding_topic_flowchart.png differ diff --git a/docs/source/_static/img/topics_adding_unit_plan_flowchart.png b/docs/source/_static/img/topics_adding_unit_plan_flowchart.png new file mode 100644 index 000000000..23d48a118 Binary files /dev/null and b/docs/source/_static/img/topics_adding_unit_plan_flowchart.png differ diff --git a/docs/source/_static/img/topics_overview_diagram.png b/docs/source/_static/img/topics_overview_diagram.png new file mode 100644 index 000000000..a5c9b1aff Binary files /dev/null and b/docs/source/_static/img/topics_overview_diagram.png differ diff --git a/docs/source/_templates/layout.html b/docs/source/_templates/layout.html new file mode 100644 index 000000000..bd5f4aa12 --- /dev/null +++ b/docs/source/_templates/layout.html @@ -0,0 +1,4 @@ +{% extends "!layout.html" %} +{% block extrahead %} + +{% endblock %} diff --git a/docs/source/author/content_style_guide.rst b/docs/source/author/content_style_guide.rst new file mode 100644 index 000000000..56d3ef379 --- /dev/null +++ b/docs/source/author/content_style_guide.rst @@ -0,0 +1,234 @@ +Content Style Guide +############################################################################## + +.. note:: + + This page is currently intended for internal staff only. + A version for all contributors will be added once the project is ready for external contributions. + + +.. contents:: Contents + :local: + + +Writing Style +============================================================================== + +When using pronouns in reference to a hypothetical person, gender neutral pronouns (they/their/them) should be used. + +All documents (other than those for internal use only) must be written clearly and simply so that a non-expert is able to understand them. +Preferably documents should be readable by students. + +Any jargon used needs to be clearly explained and should be considered as a glossary definition. + + +Capitalisation Rules +------------------------------------------------------------------------------ + +In the majority of cases capitalisation should not be used for keywords and titles, with the following exceptions, where the phrase refers to a commonly used term that is often capitilised in the literature: + +- Computer Science. +- Computational Thinking. +- Digital Technologies (note that this is the correct form to refer to the subject area in NZ, with caps and plural; if it's referring to something other than the subject then use lower case e.g. "smartphones and other digital technologies", or even better, avoid the phrase e.g. "smartphones and other digital devices"). +- Sorting Network. +- Numeracy. +- Literacy. + +The following wouldn't be capitalised: +binary number(s), digits, binary digits + + + +Google Drive Folder Structure +============================================================================== + +Including Images +------------------------------------------------------------------------------ + +All images included in a document have a comment which links to the original image file. + + +Completed Documents +------------------------------------------------------------------------------ + +Once a document is completed: + +1. Prefix the document name with ``COMPLETE -``, e.g. ``Unit plan`` will become ``COMPLETE - Unit plan``. + +2. Put a note in the document header: + + *A later version is under development, and will be released later in 2017. + No further updates will be made to this document.* + +3. Mark the document as ``Finished in Google Docs/Ready for GitHub`` by changing the cell background to green on the Scorecard. + +3. Do not edit the document any further! + + +Topic Folder +------------------------------------------------------------------------------ + +Each topic has its own folder in the ``Draft units and lessons`` folder, which contains all content relevant to that topic. +A topic folder contains unit plan folders as they are in the scorecard. +This applies to all topics even if they only contain one unit. +The structure of the topic folder, and folder and document names, are as they are below: + +.. code-block:: none + + └── topic-name/ + ├── Programming challenges/ + ├── Resources/ + ├── unit-name Unit/ + │ ├── Lessons/ + │ │ ├── 5-7/ + │ │ ├── 8-10/ + │ │ └── 11-14/ + │ ├── Ideas + │ └── Unit plan + ├── Glossary + ├── Topic description + ├── Curriculum integration + └── Diff file + +Note: + +- If lessons do not cover all age groups then the relevant folder(s) should be omitted. + +- The ``Diff file`` is only required if the folder contains documents that are complete. + + +Glossary +------------------------------------------------------------------------------ + +The following are added to the glossary and linked to where the words are used: + +- All Computer Science, programming, and Computational thinking jargon. +- All Education jargon. +- All curriculum language that is not broadly used internationally. + + +Topic description +------------------------------------------------------------------------------ + +Contains the description of the topic. +This description applies to all the units within the topic. +It is one introductory paragraph, less than 150 words, which gives a big picture overview of why this topic is being taught/is relevant, and what it will cover. + + +Curriculum integration +------------------------------------------------------------------------------ + +Contains idea and instruction cards for incorporating the unit content into lessons with other subjects (e.g. writing, art, mathematics, etc). +They are written with the intent that a teacher could print them out and laminate them. +Cards are short and preferably half a page - two pages in length (including any pictures). + + +Diff file +------------------------------------------------------------------------------ + +Until a completed document has been entered into GitHub any proposed edits are +noted in the diff file. +Once a document has been fully added to GitHub then the notes are moved from the Diff file to GitHub, and all future edits for the document are entered as issues on GitHub. + +When adding a suggested edit to the diff file: + +- It is listed under the heading corresponding to the name of the document. + +- All typo and grammar fixes include both the incorrect and corrected version in quotation marks. + +- Any links that need to be added are included with the text for the link, and links that need to be changed include the current and new links, including links for image files. + + +Unit Folder +------------------------------------------------------------------------------ + +Ideas +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Dumping ground document for ideas for future lessons, units, curriculum integrations etc. This doc won't be moved to GitHub so is always open for edits. + + +Unit plan +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The `Unit Plan Template is here`_. +Sections in italics are descriptions of sections/words that are not are to appear in the final unit plan. + +Lessons +------------------------------------------------------------------------------ + +The `Lesson Template is here`_. +Sections in italics are descriptions of sections/words that are not are to appear in the final lesson. + +Separate folders are used for each age group: 5-7, 8-10, and 11-14. +Lessons are numbered. + +If the same, identical, lesson is used for multiple age groups the file is copied to each folder and a note is added to the top of the document saying “This is identical to ”. The original document that is linked to should be the one in the lowest age group, e.g. if the same lesson occurs for 8-10 and 11-14, then the 11-14 copy should contain the note “This is identical to ”, rather than the 8-10 lesson. + + +Learning Outcomes +------------------------------------------------------------------------------ + +Each learning outcome has a unique text value, unique key, and can be used throughout every topic. +The list of learning outcomes can be found in the Scorecard and are named according to the rules described in the sections below. + + +Text value +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The text value is the text that will be displayed in the learning outcomes sections on the website. +These are written using language familiar to teachers and simple enough that it is understandable for students. +Learning outcomes always begin with a verb. + + +Key +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Learning outcome keys need to follow these rules: + +- Words in keys are written in lowercase and are separated with a hyphen e.g. ``programming-outline-variable-updates``. + +- The order of words in the key are: + + 1. The topic area of the outcome. + 2. If the topic area is cross-curricula, the curriculum area of the outcome. + 3. The verb used at the beginning of the text value. + 4. Keywords describing the content of the outcome. + +There is no limit on the length of keys as it is important they are as descriptive as possible. + + +Programming Challenges +============================================================================== + +There needs to be enough scaffolding to support students to be able to achieve a result, independently. + +When transferrring a programming challenge from Google Docs to GitHub, these are the rules to follow: + + +1. Separate out all the blocks that “click” together, leaving all the information inside where the parameter is written. All duplicates of a block should be displayed. + +2. The order of the blocks should always be randomised. If there are more than six blocks the blocks should be split into groups by colour, and then randomise the order of the blocks in these groups. This is the order the colour groups should be displayed in: + +.. image:: ../_static/img/scratch_block_types.png + :alt: A image showing the order to display colour groups in. + +2. Where a variable is inserted into another block, those blocks stay together, example below: + +.. image:: ../_static/img/scratch_say_block.png + :alt: A screenshot of a say block containing a variable. + +3. All join blocks are displayed as one and all the variables/text are included, example below: + +.. image:: ../_static/img/scratch_multiple_join_blocks.png + :alt: A screenshot of several join blocks together. + +4. For blocks containing join blocks keep the join block within the parent block, example below. + +.. image:: ../_static/img/scratch_set_block.png + :alt: A screenshot of a set block containing a join block. + +5. Loops should keep the condition blocks, but the blocks within the loop should be extracted. + +.. _Unit Plan Template is here: https://docs.google.com/document/d/1DBwrpKy9sulDq_O1vKQoLapTuhzIIq3iHIcwKKvCKK8/ +.. _Lesson Template is here: https://docs.google.com/document/d/1uUN7kPsTlyIGEnmAxTDNat7S4yKM5VNzrnmtTnegSaQ/edit \ No newline at end of file diff --git a/docs/source/author/index.rst b/docs/source/author/index.rst new file mode 100644 index 000000000..b7aed4987 --- /dev/null +++ b/docs/source/author/index.rst @@ -0,0 +1,15 @@ +Author Documentation +############################################################################## + +The following pages are for those wanting to write or edit CS Unplugged content. + +.. toctree:: + :maxdepth: 1 + :caption: Contents + + writing_guide + writing_philosophy + understanding_configuration_files + topics + content_style_guide + translations diff --git a/docs/source/author/topics.rst b/docs/source/author/topics.rst new file mode 100644 index 000000000..85b9f0b92 --- /dev/null +++ b/docs/source/author/topics.rst @@ -0,0 +1,1017 @@ +Topic Content +############################################################################## + +The topics application (see :ref:`what-is-an-application`) is the main focus of +the CS Unplugged website, as it contains the majority of educational material +for the project. + +.. contents:: Contents + :local: + +Topics Overview +============================================================================== + +A general overview of the topics application can be described in the following +diagram. + +.. The following image can copied for be edits here: https://goo.gl/Vjv6XV +.. image:: ../_static/img/topics_overview_diagram.png + :alt: A diagram providing an overview of topics application content + +- The application is made up of **topics**. + + - A **topic** must contain at least one **unit plan**. + + - A **unit plan** must contain at least one **lesson**. + + - A **lesson** can contain **learning outcomes**, and + **generated resources**. + + - A **topic** can also contain **curriculum integrations**, which can also contain + **curriculum areas**. + + - A **topic** can also contain **programming challenges**. + + - A **programming challenge** can use different **programming languages**, and be set at + a specific **difficulty**. + +- **Learning outcomes**, **curriculum areas**, **glossary definitions**, + **programming languages**, and **programming challenge difficulties** are + defined at a language level, so can be used by all topic content + of that language. + +This is just a broad overview of the topics application. + +Viewing All Topics Content +------------------------------------------------------------------------------ + +When developing locally, once you have run ``./csu start`` (see +:doc:`../getting_started/helper_commands`) you can go to the url below to get a +quick overview of what content is loaded: + +.. code-block:: none + + localhost/__dev__/ + +For more information about what this page displays, see :doc:`../developer/dev`. + +.. _topics-directory-structure: + +Topics Content Directory +============================================================================== + +The diagram below is an example of the ``content/en/`` language directory for +the project's topic application, where: + +- Blue is directories. +- Red is YAML configuration files (see :doc:`understanding_configuration_files`). +- Green is Markdown text files. + +.. raw:: html + :file: ../_static/html_snippets/topics_content_directory_tree.html + +.. _adding-topics-content: + +Adding Content +============================================================================== + +The following flow charts will take you step by step through the process of adding new +content to the topics application. Below this section is full details on how to structure +and write the configuration files for the topics application. + +.. _adding-a-topic: + +Adding a Topic +------------------------------------------------------------------------------ + +.. The following image can copied for be edits here: https://goo.gl/Vjv6XV +.. The image is included as raw HTML because it has clickable nodes. +.. raw:: html + + + + + + + + + +The Markdown file containing the description of the topic: + +- **Is in:** the topic directory, e.g. the description file for + Binary Numbers will be in ``topics/content/en/binary-numbers/``. +- **Is called:** ``.md`` where ```` is the key + (:ref:`what-is-a-key`) of the topic and the name of the directory it is in, + e.g. ``binary-numbers.md`` or ``kidbots.md``. +- **Contains:** An H1 heading (i.e. has a single ``#`` prefix) and the content + of the description. + +.. note :: + + The heading written in this file will be used exactly as it is given + throughout the website as the name of the topic. + +.. warning:: + + Every topic needs at least one unit plan, therefore the system will not allow + a topic to be loaded until a unit plan is connected to it. + +.. _adding-a-unit-plan: + +Adding a Unit Plan +------------------------------------------------------------------------------ + +.. The following image can copied for be edits here: https://goo.gl/Vjv6XV +.. The image is included as raw HTML because it has clickable nodes. +.. raw:: html + + + + + + + + + + +The Markdown file containing the content og the unit plan: + +- **Is in:** the unit plan directory, e.g. the unit plan file for Binary Numbers + Unit Plan 2 will be in ``topics/content/en/binary-numbers/unit-plan-2/``. +- **Is called:** ``.md`` where ```` is the key + (:ref:`what-is-a-key`) of the unit plan and the name of the directory it is + in, e.g. ``unit-plan-2.md``. +- **Contains:** An H1 heading (i.e. has a single ``#`` prefix) and the content + of the unit plan. + +.. note:: + + The heading written in this file will be used exactly as it is given + throughout the website as the name of the unit plan. + +.. warning:: + + Every unit plan needs at least one lesson, so the system will not allow a + unit plan to be loaded until a lesson is connected to it. + +.. _adding-a-lesson: + +Adding a Lesson +------------------------------------------------------------------------------ + +.. The following image can copied for be edits here: https://goo.gl/Vjv6XV +.. The image is included as raw HTML because it has clickable nodes. +.. raw:: html + + + + + + + + + +The Markdown file containing the content for the lesson: + +- **Is in:** the lesson subdirectory in the unit plan directory, e.g. + ``topics/content/en/binary-numbers/unit-plan/lessons/``. +- **Is called:** ``.md`` where ```` is the key + (:ref:`what-is-a-key`) of the lesson, e.g. ``introduction-to-bits.md``. +- **Contains:** An H1 heading (i.e. has a single ``#`` prefix) and the content + for the lesson. + +.. note:: + + The heading written in this file will be used exactly as it is given + throughout the website as the name of the lesson. + +.. note:: + + If a lesson includes programming challenges, Computational Thinking links, + and/or learning outcomes, then the corresponding configuration and content + files may also need to be added or updated. + +.. _adding-learning-outcomes: + +Adding Learning Outcomes +------------------------------------------------------------------------------ + +.. The following image can copied for be edits here: https://goo.gl/Vjv6XV +.. The image is included as raw HTML because it has clickable nodes. +.. raw:: html + + + + + + + + + +You will now be able to add learning outcomes to lessons and programming +challenges by referencing the keys you specified in the learning outcomes configuration +file. + +.. note:: + + If a learning outcome contains curriculum areas, then the curriculum areas + configuration file may also need to be added or updated. + +.. _adding-curriculum-areas: + +Adding Curriculum Areas +------------------------------------------------------------------------------ + +.. The following image can copied for be edits here: https://goo.gl/Vjv6XV +.. The image is included as raw HTML because it has clickable nodes. +.. raw:: html + + + + + + + + + +You will now be able to add curriculum areas to learning outcomes and curriculum +integrations by referencing the keys you specified in the curriculum areas +configuration file. + +.. _adding-a-programming-challenge: + +Adding a Programming Challenge +------------------------------------------------------------------------------ + +.. The following image can copied for be edits here: https://goo.gl/Vjv6XV +.. The image is included as raw HTML because it has clickable nodes. +.. raw:: html + + + + + + + + + + +You will now be able to add programming challenges to lessons by referencing the +keys you specified in the programming challenges configuration file. + +A programming challenge is split into several different sections, each of which +is an its own Markdown file, all of which are in +``topics/content/en/binary-numbers/programming-challenges//`` +where ```` refers to the key (:ref:`what-is-a-key`) of the +challenge, e.g. ``count-to-16``. + + 1. The challenge description: + + - **Is called:** ``.md`` where ```` is the key + of the challenge, e.g. ``count-to-16.md``. + - **Contains:** An H1 heading (i.e. has a single ``#`` prefix) and the content + of the challenge. + + .. note:: + + The heading written in this file will be used exactly as it is given + throughout the website as the name of the programming challenge. + + 2. The expected output + + - **Is called:** ``-expected.md`` where ```` is the key + of the programming language, e.g. ``python-expected.md``. + - **Contains:** The expected output for the programming challenge, e.g. an + embedded Scratch program or Python output. + + 3. Hints (optional) + + - **Is called:** ``-hints.md`` where ```` is the key + of the programming language, e.g. ``scratch-hints.md``. + - **Contains:** Hints for how to complete the challenge, e.g. suggested + Scratch blocks. + + 4. Example solution(s) + + - **Is called:** ``-solution.md`` where ```` is the key + of the programming language, e.g. ``ruby-solution.md``. + - **Contains:** Example solutions to the challenge, e.g. Scratch program. + + 5. Extra challenge(s) (optional) + + - **Is called:** the value defined in the programming challenges + configuration file. + A common filename is ``extra-challenge.md``. + - **Contains:** Content for an extra challenge. + +2-4 from the list above can be given in multiple programming languages. +Therefore, the languages you have chosen must be specified in the +``programming-challenges.yaml`` configuration file, as well as the +``programming-challenges-structure.yaml`` configuration file. + +.. note:: + + If the challenge includes learning outcomes, then the corresponding configuration + file will also need to be added or updated to include new learning outcomes. + +.. _adding-a-curriculum-integration: + +Adding a Curriculum Integration +------------------------------------------------------------------------------ + +.. The following image can copied for be edits here: https://goo.gl/Vjv6XV +.. The image is included as raw HTML because it has clickable nodes. +.. raw:: html + + + + + + + + + +The Markdown file containing the content of the curriculum integration: + +- **Is in:** the curriculum integration directrory, e.g. curriculum integrations + in Binary Numbers will be in + ``topics/content/en/binary-numbers/curriculum-integrations/``. +- **Is called:** ``.md`` where ```` is the key + (:ref:`what-is-a-key`) of the curriculum integration, e.g. ``whose-cake-is-it.md``. +- **Contains:** An H1 heading (i.e. has a single ``#`` prefix) and the content + of the integration. + +.. note :: + + If the integration includes curriculum areas and/or prerequisite lessons, + then the corresponding configuration and content files will also need to be added. + +.. _adding-glossary-definitions: + +Adding Glossary Definitions +------------------------------------------------------------------------------ + +.. The following image can copied for be edits here: https://goo.gl/Vjv6XV +.. The image is included as raw HTML because it has clickable nodes. +.. raw:: html + + + + + + + + +.. _glossary-definitions-markdown-file: + +Each glossary definition requires a Markdown file within the glossary +folder, with the filename as the glossary key. +When linking text to a glossary definition, the key is used as the identifier. +For example, with the key ``pixel``, then a file ``pixel.md`` is +expected. + +Each Markdown file should start with a heading containing the glossary term +(this should be capitalized and include any required punctuation), followed +by the term's definition. + +Continuing the ``pixel.md`` example from above, this could be the possible +contents of that file. + +.. code-block:: none + + # Pixel + + This term is an abbreviation of picture element, the name given to the + tiny squares that make up a grid that is used to represent images on a + computer. + +Configuration Files +============================================================================== + +This section details configuration files within the ``content`` directory for a specific +language. +These files are in YAML format. If you are not familiar with YAML, see +:doc:`understanding_configuration_files`. + +The diagram below shows an example of YAML file locations for the +``content/en/`` language directory, where: + +- Blue is directories. +- Red is YAML configuration files. + +.. raw:: html + :file: ../_static/html_snippets/topics_content_directory_tree_only_yaml.html + +In the following sections, each configuration file is exaplained in more detail. + +.. note:: + + - Some of the keys (:ref:`what-is-a-key`) have angle brackets around them, + ````. This means that they are variables and you can call them + whatever you like in your configuration file (without the angle brackets). + +.. _application-structure-file: + +Application Structure Configuration File +------------------------------------------------------------------------------ + +- **File Name:** ``structure.yaml`` + +- **Location:** ``topics/content//`` + +- **Purpose:** Defines the top level configuration files to process for defining + the content of the topics application. + +- **Required Fields:** + + - ``topics:`` A list of file paths to topic configuration files. + +- **Optional Fields:** + + - ``learning-outcomes:`` The path to the learning outcomes configuration file. + - ``curriculum-areas:`` The path to the curriculum areas configuration file. + - ``programming-challenges-structure:`` The path to the programming exercies structure + configuration file. + - ``glossary-folder:`` The folder name that contains the Markdown files for + glossary definitions. + +A complete application structure file may look like the following: + +.. code-block:: yaml + + topics: + - binary-numbers + - error-detection-correction + + learning-outcomes: learning-outcomes.yaml + curriculum-areas: curriculum-areas.yaml + programming-challenges-structure: programming-challenges-structure.yaml + + glossary-folder: glossary + +.. _topic-file: + +Topic Configuration File +------------------------------------------------------------------------------ + +- **File Name:** ``.yaml`` + +- **Location:** ``topic/content///`` + +- **Referenced In:** ``topic/content//structure.yaml`` + +- **Purpose:** This file defines the attributes of a specific topic, including connected + unit plan, programming challenge, and curriculum integration configuration files. + +- **Required Fields:** + + - ``unit-plans:`` A list of keys, where each key is a unit plan. + +- **Optional Fields:** + + - ``icon:`` An image file to be used as the icon for the topic. + + - ``other-resources:`` A Markdown file containing information about other related + (external) resources. + + - ``programming-challenges:`` The path to the programming challenges configuration file. + + - ``curriculum-integrations:`` The path to the curriculum integrations configuration + file. + +A complete topic structure file may look like the following: + +.. code-block:: yaml + + unit-plans: + - unit-plan + - unit-plan-2 + + icon: img/binary-numbers-0-1.png + + other-resources: other-resources.md + + programming-challenges: programming-challenges/programming-challenges.yaml + curriculum-integrations: curriculum-integrations/curriculum-integrations.yaml + +.. _unit-plan-file: + +Unit Plan Configuration File +------------------------------------------------------------------------------ + +- **File Name:** ``.yaml`` + +- **Location:** ``topic/content////`` + +- **Referenced In:** ``topic/content///.yaml`` + +- **Purpose:** This file defines which lessons to use in each age group + + - **Required Fields:** + + - ``lessons:`` The path to the lessons configuration file. + + - ``age-groups:`` Keys of age groups and their corresponding lessons. + + - **Required Fields:** + + - ``:`` The key for the age group. + + - **Required Fields:** + + - ```` The key for a lesson. + + - **Required Fields:** + + - ``number`` The number order for this lesson, relative + to this age group. + This value allows a lesson to be used in different age + groups, as different numbered lessons (e.g. lesson 2 for + 5 to 7, but lesson 1 for 8 to 10). + + - **Optional Fields:** + + - ``computational-thinking-links``: The Markdown filename containing + Computational Thinking links. + +A complete unit plan structure file with multiple lessons may look like the +following: + +.. code-block:: yaml + + lessons: lessons/lessons.yaml + + age-groups: + 5-7: + what-is-binary-junior: + number: 1 + how-binary-digits-work: + number: 2 + 8-10: + how-binary-digits-work: + number: 1 + reinforcing-sequencing-in-binary-number-systems: + number: 2 + codes-for-letters-using-binary-representation: + number: 3 + + +Lesson Configuration File +------------------------------------------------------------------------------ + +- **File Name:** ``.yaml`` + +- **Location:** ``topic/content////lessons/`` + +- **Referenced In:** ``topic/content////.yaml`` + +- **Purpose:** This file defines all the lessons (and their respective) + attributes for the unit plan. + + - **Required Fields:** + + - ``:`` This is the key for the lesson. Each lesson has its own list of + required and optional fields: + + - **Optional Fields:** + + - ``duration``: The estimated time to complete the lesson (in minutes). + + - ``computational-thinking-links``: The Markdown filename containing + Computational Thinking links. + + - ``programming-challenges:`` A list of keys corresponding to programming + challenges. + + - ``programming-challenges-description``: The Markdown filename + containing a description for the programming challenges. + + - ``learning-outcomes:`` A list of keys corresponding to learning outcomes. + + - ``classroom-resources:`` A list of strings describing the required + classroom resources. + The list items must be short (less than 100 characters), + as this list is displayed on the lesson sidebar. + If a longer description is required, this should be within the lesson + text within a panel. + + - ``generated-resources:`` A list of generated CSU resources connected to this + lesson. + + - **Required Fields:**: + + - ````: The key corresponding to the resource. + + - **Required Fields:**: + + - ``description:`` A description of how the resource should be used. + +A complete lesson structure file with multiple lessons may look like the +following: + +.. code-block:: yaml + + introduction-to-bits: + programming-challenges: + - count-to-16 + - count-to-1-million + learning-outcomes: + - binary-data-representation + generated-resources: + sorting-network: + description: One per student. + classroom-resources: + - Pens and paper + - Dice + + how-binary-digits-work: + computational-thinking-links: how-binary-digits-work-ct-links.md + learning-outcomes: + - binary-data-representation + - binary-justify-representation + +.. _age-groups-file: + +Age Group Configuration File +------------------------------------------------------------------------------ + +- **File Name:** ``age-groups.yaml`` + +- **Location:** ``topics/content//`` + +- **Referenced In:** ``topics/content//structure.yaml`` + +- **Purpose:** Defines the age groups avilable for all lessons. + +- **Required Fields:** + + - ``:`` This is the key for the age group. + Each age group has its own list of required and optional fields: + + - **Required Fields:** + + - ``min_age:`` The minimum age of the age group. + - ``max_age:`` The maximum age of the age group. + + - **Optional Fields:** + + - ``description:`` A text description for the age group. + +A complete age group structure file may look like the following: + +.. code-block:: yaml + + 5-7: + min_age: 5 + max_age: 7 + description: In the teacher observations sections there may also be background notes on the big picture. There is no expectation that 5 to 7 year olds will need to know this, but if you are asked, you have the answer at your fingertips. + 8-10: + min_age: 8 + max_age: 10 + 11-14: + min_age: 11 + max_age: 14 + +.. _learning-outcomes-file: + +Learning Outcomes Configuration File +------------------------------------------------------------------------------ + +- **File Name:** ``learning-outcomes.yaml`` + +- **Location:** ``topics/content//`` + +- **Referenced In:** ``topics/content//structure.yaml`` + +- **Purpose:** Defines the learning outcomes avilable for all topics. + +- **Required Fields:** + + - ``:`` This is the key for the learning outcome. + Each learning outcome has its own list of required and optional fields: + + - **Required Fields:** + + - ``text:`` The text of the learning outcome (this is what will + be displayed to the user). + + - **Optional Fields:** + + - ``curriculum-areas:`` A list of curriculum area key (see example file below). + +A complete learning outcome structure file may look like the following: + +.. code-block:: yaml + + no-physical-zeros-ones: + text: Justify why there aren’t actual 0’s and 1’s zooming around inside a computer. + curriculum-areas: + - computational-thinking + + binary-correct-representation: + text: Argue that 0’s and 1’s are still a correct way to represent what is stored in the computer. + curriculum-areas: + - computational-thinking + - data-representation + + maths-comparing-numbers: + text: Compare numbers + curriculum-areas: + - numeracy + +.. _curriculum-areas-file: + +Curriculum Areas Configuration File +------------------------------------------------------------------------------ + +- **File Name:** ``curriculum-areas.yaml`` + +- **Location:** ``topics/content//`` + +- **Referenced In:** ``topics/content//structure.yaml`` + +- **Purpose:** Defines the curriculum areas available for all topics. + +- **Required Fields:** + + - ``:`` This is the key for the curriculum area. Each curriculum + area has its own list of required and optional fields: + + - **Required Fields:** + + - ``name:`` The name of the curriculum area (this is what will be displayed to the + user). + - ``number:`` A number used for ordering curriculum areas. + Areas are sorted in ascending numbers (smallest to largest). + - ``colour:`` The CSS colour class to use for colouring the curriculum + area badge on the website. + This colour is also applied to all children of curriculum area. + + Available colours include: + + - ``blue`` + - ``green`` + - ``light-purple`` + - ``orange`` + - ``pink`` + - ``purple`` + - ``red`` + - ``teal`` + - ``yellow`` + + These colours are defined in: ``csunplugged/static/scss/website.scss``. + + - **Optional Fields:** + + - ``children:`` A list of sub-curriculum areas (see example file below). Each child + requires a ``name`` field. + Children inherit the same colour and number as their parent. + +An example curriculum areas file with multiple curriculums may look like +the following: + +.. code-block:: yaml + + maths: + name: Maths + colour: green + children: + geometry: + name: Geometry + algebra: + name: Algebra + + science: + name: Science + colour: blue + + art: + name: Art + colour: teal + +.. note:: + + The maximum depth for children is one, that is, children curriculum areas + cannot have children. + +.. note:: + + When including a curriculum area in another configuration file, adding a child + curriculum area will automatically add the parent curriculum area, you do not need to + specify this manually. For example, adding ``geometry`` means that ``maths`` is + automatically included. + +.. _programming-challenges-structure-file: + +Programming Challenges Structure Configuration File +------------------------------------------------------------------------------ + +- **File Name:** ``programming-challenges-structure.yaml`` + +- **Location:** ``topics/content//`` + +- **Referenced In:** ``topics/content//structure.yaml`` + +- **Purpose:** This file defines the structure of programming challenges for all + topics. + +- **Required Fields:** + + - ``languages:`` A list of languages that programming challenges can be given in. + + - **Required Fields:** + + - ``:`` This is the key for the language. Each language has its own + list of required and optional fields: + + - **Required Fields:** + + - ``name:`` The name of the programming language (this is what will be + displayed to the user). + + - ``number:`` A number used for ordering programming languages. + Languages are sorted in ascending numbers (smallest to largest). + + - **Optional Fields:** + + - ``icon:`` An image file to be used as the icon for the language. + + - ``difficulties:`` A list of difficulties programming challenges can be labelled as. + + - **Required Fields:** + + - ``:`` An integer value. + + - **Required Fields:** + + - ``name:`` The name of the difficulty level (this is what will be displayed to + the user). + +A complete programming challenge structure file may look like the following: + +.. code-block:: yaml + + language: + scratch: + name: Scratch + number: 1 + icon: img/scratch-cat.png + ruby: + name: Ruby + number: 2 + + difficulties: + 1: + name: Beginner + 2: + name: Intermediate + 3: + name: Advanced + +.. _programming-challenges-file: + +Programming Challenges Configuration File +------------------------------------------------------------------------------ + +- **File Name:** ``programming-challenges.yaml`` + +- **Location:** ``topics/content///programming-challenges/`` + +- **Referenced In:** ``topics/content///.yaml`` + +- **Purpose:** This file defines the programming challenges (and their respective attributes) + for a particular topic. + +- **Required Fields:** + + - ```` + + - **Required Fields:** + + - ``challenge-set-number:`` The group of related programming challenges this + challenge belongs to (see note below). + + - ``challenge-number:`` The number order for this programming challenge (see note below). + + - ``difficulty-level:`` A key corresponding to a difficulty level. + + - ``programming-languages:`` A list of keys corresponding to programming languages + that this challenge is given in. + + - **Optional Fields:** + + - ``learning-outcomes:`` A list of keys corresponding to learning outcomes. + + - ``extra-challenge:`` A Markdown filename containing the content for an + extra challenge. + +.. note :: + + Programming challenges are sorted by their ``challenge-set-number`` + and then their ``challenge-number``. + These numbers are not directly displayed, but used to calculate a + programming challenge's number for a lesson. + + For example, if a lesson lists the following challenges: + + - Challenge A: 1.1 + - Challenge B: 1.3 + - Challenge C: 2.2 + - Challenge D: 9.3 + + The lesson will display these challenges as: + + - Challenge A: 1.1 + - Challenge B: 1.2 + - Challenge C: 2.1 + - Challenge D: 3.1 + +A complete programming challenges structure file may look like the following: + +.. code-block:: yaml + + count-to-16: + challenge-set-number: 1 + challenge-number: 1 + difficulty-level: 1 + programming-languages: + - ruby + - python + learning-outcomes: + - programming-sequence + + count-to-a-million: + challenge-set-number: 1 + challenge-number: 2 + difficulty-level: 3 + programming-languages: + - python + learning-outcomes: + - programming-basic-logic + extra-challenge: extra-challenge.md + +.. _curriculum-integrations-file: + +Curriculum Integrations Configuration File +------------------------------------------------------------------------------ + +- **File Name:** ``curriculum-intergrations.yaml`` + +- **Location:** ``topics/content///`` + +- **Referenced In:** ``topics/content//.yaml`` + +- **Purpose:** Contains a list of curriculum integrations that can be used to integrate + the topic with another area in the curriculum. + +- **Required Fields:** + + - ``:`` This is the key for the curriculum integration. Each + curriculum integration has its own list of required and optional fields: + + - **Required Fields:** + + - ``number:`` The number order for this curriculum integration. Curriculum + integrations are sorted by this number. + + - ``curriculum-areas:`` A list of keys corresponding to other curriculum areas + that this curriculum integration could be used in. + + - **Optional Fields:** + + - ``prerequisite-lessons:`` A list of unit plan keys containing lessons that are + expected to be completed before attempting this curriculum integration. + + - **Required Fields:** + + - ``:`` A key corresponding to a unit plan. + + - **Required Fields:** + + - ```` A key corresponding to a lesson in the given unit + plan. + +A complete curriculum integration structure file with multiple curriculum integrations +may look like the following: + +.. code-block:: yaml + + binary-number-bracelets: + number: 1 + curriculum-areas: + - math + - art + prerequisite-lessons: + unit-plan: + - introduction-to-binary-digits + unit-plan-2: + - counting-in-binary + + binary-leap-frog: + number: 2 + curriculum-areas: + - math + - pe + prerequisite-lessons: + unit-plan-2: + - counting-in-binary diff --git a/docs/source/author/translations.rst b/docs/source/author/translations.rst new file mode 100644 index 000000000..777b65e0e --- /dev/null +++ b/docs/source/author/translations.rst @@ -0,0 +1,5 @@ +Translations +############################################################################## + +Currently translations are not supported with the Django system, but this will +be implemented for the ``1.0.0`` release in late 2017. diff --git a/docs/source/author/understanding_configuration_files.rst b/docs/source/author/understanding_configuration_files.rst new file mode 100644 index 000000000..f5723e263 --- /dev/null +++ b/docs/source/author/understanding_configuration_files.rst @@ -0,0 +1,71 @@ +Understanding Configuration Files +############################################################################## + +There is a lot of content within the CS Unplugged project. +We split this content across many files and configuration files are the things +that bring everything together. +These files are used for configuring the content data when stored in the +system database, so it's important to understand how to read and write these +configuration files for working on this project. + +Here is an example configuration file used to define follow up activities +in the CS Unplugged project: + +.. code-block:: yaml + + binary-number-bracelets: + number: 1 + md-file: bracelets.md + curriculum-areas: + - arts + - design + + hidden-binary-signals: + number: 2 + md-file: hidden-binary-signals.md + curriculum-areas: + - science + +This page aims to give a brief tutorial on YAML files, so you can modify +configuration files within this project. + +YAML files are mostly made up of key/value pairs, often called a dictionary +within programming languages. +This configuration file contains the follow three key/value pairs: + +- The key ``learning-outcomes`` points to the file + ``learning-outcomes.yaml`` +- The key ``programming-challenges-structure`` points to the file + ``programming-challenges-structure.yaml`` +- The key ``topic-structure-files`` points to the ordered list of files + ``binary-numbers/structure.yaml`` and + ``error-detection-and-correction/structure.yaml`` + +The majority of configuration files within this project only use dictionaries +and lists to store their data. +Here are some other useful tips: + +.. code-block:: yaml + + # You can include comments in YAML by starting with a # character + + # This stores the integer 7 in the key 'number' + number: 7 + + # This is an ordered list of dictionaries within the key 'difficulties' + difficulties: + - level: 1 + name: Beginner + - level: 2 + name: Growing Experience + - level: 3 + name: Ready to Expand + +You may find that there is more than one configuration file that you need to +modify/create. +This is because it is difficult to read files using deep nesting +(indentation), so we have split configuration data across multiple files to +avoid this issue. + +If you want to learn more about YAML, there are plenty of great tutorials +available on the internet. diff --git a/docs/source/author/writing_guide.rst b/docs/source/author/writing_guide.rst new file mode 100644 index 000000000..3009f68c2 --- /dev/null +++ b/docs/source/author/writing_guide.rst @@ -0,0 +1,455 @@ +Writing Guide +############################################################################## + +The majority of our text content is written in Markdown, and we also developed +a program called `Verto`_ to allow you to include HTML elements like images and +videos with simple text tags. + +For example, the following text: + +.. code-block:: none + + # Scratch Programming Challenge + + Try using the following blocks to create a program to display numbers 1, 2, + 4, 8 and 16 on the screen, one at a time. + + + ```scratch + when flag clicked + + say [Hello] for [2] secs + ``` + + Your program should look like the following: + + {iframe link="http://scratch.mit.edu/projects/embed/148423714/?autostart=false"} + + {panel type="note" title="Tips"} + + - Make sure all your blocks are “snapped” together in a line like a + jigsaw puzzle. + + {panel end} + + {button-link link="challenge/scratch-solution.html" text="Scratch Solution"} + +will display as the following on the website: + +.. image:: ../_static/img/markdown_example_rendered.png + :alt: An image showing the above Markdown syntax rendered as HTML + +.. note:: + + If you already know Markdown syntax, please remember the following project + preferences (for consistency and readability): + + - Use asterisks (``*``) for emphasis, instead of underscores. + - Use hyphens (``-``) for unordered lists. + - No HTML within text files, we use Verto text tags to add iframes, + images, videos, etc. + +Below is a basic guide to syntax for Markdown and Verto text tags. +When viewing Verto documentation for a tag, the top of the page will detail +how to use the tag in a basic example. +Some text tags also have required and/or optional tag parameters for further +configuration. + +.. contents:: Text Syntax + :local: + +------------------------------------------------------------------------------ + +Blockquotes +============================================================================== + +.. code-block:: none + + > Blockquotes are very handy to emulate reply or output text. + > This line is part of the same quote. + + Quote break. + + > Oh, you can *put* **Markdown** into a blockquote. + +------------------------------------------------------------------------------ + +Boxed Text (Verto feature) +============================================================================== + +`Click here to read the documentation on how to box text`_. + +------------------------------------------------------------------------------ + +Code +============================================================================== + +You are able to include code snippets, either in a line of text or as a new +block. + +To include inline code, add a backtick to either side of the code. +For example: \`print("Hi")\` will display as ``print("Hi")``. +You cannot set the language syntax highlighting for inline code. + +To create a code block, use a line of three backticks before and after the +code. You also can add syntax highlighting by specifying the language after +the first set of backticks (`list of language codes`_). + +.. code-block:: none + + ```python3 + def find_high_score(scores): + if len(scores) == 0: + print("No high score, table is empty") + return -1 + else: + highest_so_far = scores[0] + for score in scores[1:]: + if score > highest_so_far: + highest_so_far = score + return highest_so_far + ``` + +.. code-block:: python3 + + def find_high_score(scores): + if len(scores) == 0: + print("No high score, table is empty") + return -1 + else: + highest_so_far = scores[0] + for score in scores[1:]: + if score > highest_so_far: + highest_so_far = score + return highest_so_far + +------------------------------------------------------------------------------ + +Comment (Verto feature) +============================================================================== + +`Click here to read the documentation on how to add a comment`_. + +------------------------------------------------------------------------------ + +Conditional (Verto feature) +============================================================================== + +`Click here to read the documentation on how to define a conditional`_. + +------------------------------------------------------------------------------ + +Embed iframe (Verto feature) +============================================================================== + +`Click here to read the documentation on how to embed with an iframe`_. + +------------------------------------------------------------------------------ + +Emphasis +============================================================================== + +.. code-block:: none + + Emphasis, aka italics, with *asterisks*. + + Strong emphasis, aka bold, with **asterisks**. + +Emphasis, aka italics, with *asterisks*. + +Strong emphasis, aka bold, with **asterisks**. + +.. note:: + + We do not use underscores for emphasis to maintain consistency and + readability. + +------------------------------------------------------------------------------ + +Glossary Link (Verto feature) +============================================================================== + +`Click here to read the documentation on how to define a glossary link`_. + +The list of defined glossary terms available for linking to can be found in the +:ref:`application-structure-file`. + +------------------------------------------------------------------------------ + +Heading (Verto feature) +============================================================================== + +`Click here to read the documentation on how to create a heading`_. + +------------------------------------------------------------------------------ + +Image (Verto feature) +============================================================================== + +`Click here to read the documentation on how to include an image`_. + +------------------------------------------------------------------------------ + +Interactive (Verto feature) +============================================================================== + +`Click here to read the documentation on how to include an interactive`_. + +------------------------------------------------------------------------------ + +Line Breaks +============================================================================== + +Here are some things to try out: + +.. code-block:: none + + Here's a line for us to start with. + + This line is separated from the one above by two newlines, so it will be a + *separate paragraph*. + + This line is also a separate paragraph, but... + This line is only separated by a single newline, so it's a separate line + in the *same paragraph*. + +------------------------------------------------------------------------------ + +Links +============================================================================== + +There are several links that may be used: + +The general syntax for links is ``[link text](link url)`` where ``link text`` +is the text to be displayed in the document, and ``link url`` is the +destination of the link. + +**Escaping closing brackets within link URLs:** A closing bracket can be +escaped by prefixing it with a backslash ``\)``. + +Internal links +------------------------------------------------------------------------------ + +These are links to pages within the CS Unplugged website. +These links will not work when viewed in a Markdown renderer, however these +will function properly when converted to HTML and viewed on the website. +Links to pages are referenced from the language directory within the +``content/`` directory (see examples below). + +Link to Page Within Website (relative link - Verto feature) +------------------------------------------------------------------------------ + +You can refer to a page by writing the page name with ``.html`` at the end. +The name of a file is defined by it's slug in the configuration files, but +it helps to have knowledge of the resulting URL path for a file. +See the examples below: + +.. code-block:: none + + Check out [binary numbers](topics/binary-numbers.html). + Check out the [about page](about.html). + +`Click here to read the documentation on how to create a relative link`_. + +Link to Heading on Page Within Website +------------------------------------------------------------------------------ + +You can refer to a subsection on a page by following the same syntax as above +and then adding the subsection name at the end with a ``#`` separator. +All headers are subsections that have a link that can be linked to (called an +anchor link). +The anchor link can be determined by converting the header name to lowercase, +with spaces replaced with dashes, and punctuation removed. +In cases where duplicate headings exist on the same page, a number is appended +on the end of the anchor link. + +.. code-block:: none + + Please [contact us](about/index.html#contact). + +Link to a Page Outside of Website (external link) +------------------------------------------------------------------------------ + +These are links to websites that are not a part of the CS Unplugged project. +The URL should include the ``https://`` or ``http://`` as required. + +.. code-block:: none + + Check out [Google's website](https://www.google.com). + +Create a Link on an Image (Verto feature) +------------------------------------------------------------------------------ + +Images should now be linked using the ``caption-link`` and ``source`` tag +parameters for including an image. + +Create a Link on a Button (Verto feature) +------------------------------------------------------------------------------ + +`Click here to read the documentation on how to add a button link`_. + +------------------------------------------------------------------------------ + +Lists +============================================================================== + +Lists can be created by starting each line with a ``-`` for unordered lists +or ``1.`` for ordered lists. +The list needs to be followed by a blank line, however it doesn't require a +blank line before unless the preceding text is a heading (a blank line is +then required). +If you are having issues with a list not rendering correctly, try adding a +blank line before the list if there is none, otherwise `submit a bug report`_ +if you are still having rendering issues. + +.. code-block:: none + + Unordered list: + - Item 1 + - Item 2 + - Item 3 + + Ordered list: + 1. Item 1 + 2. Item 2 + 3. Item 3 + +Unordered list: + +- Item 1 +- Item 2 +- Item 3 + +Ordered list: + +1. Item 1 +2. Item 2 +3. Item 3 + +Nested lists can be created by indenting each level by 2 spaces. + +.. code-block:: none + + 1. Item 1 + 1. A corollary to the above item, indented by 2 spaces. + 2. Yet another point to consider. + 2. Item 2 + * A corollary that does not need to be ordered. + * This is indented four spaces, because it's two for each level. + * You might want to consider making a new list by now. + 3. Item 3 + +1. Item 1 + + 1. A corollary to the above item, indented by 2 spaces. + 2. Yet another point to consider. + +2. Item 2 + + * A corollary that does not need to be ordered. + + * This is indented four spaces, because it's two for each level. + * You might want to consider making a new list by now. + +3. Item 3 + +------------------------------------------------------------------------------ + +Math +============================================================================== + +To include math (either inline or as a block) use the following syntax while +using LaTeX syntax. + +.. code-block:: none + + This is inline math: ``\( 2 + 2 = 4 \)`` + + This is block math: + + ``\[ \begin{bmatrix} s & 0 \\ 0 & s \\ \end{bmatrix} \]`` + +Math equations are rendered in MathJax using the LaTeX syntax. + +------------------------------------------------------------------------------ + +Panel (Verto feature) +============================================================================== + +`Click here to read the documentation on how to create a panel`_. + +------------------------------------------------------------------------------ + +Scratch (Verto feature) +============================================================================== + +`Click here to read the documentation on how to include an image of Scratch block`_. + +------------------------------------------------------------------------------ + +Table of Contents (Verto feature) +============================================================================== + +`Click here to read the documentation on how to include a table of contents`_. + +------------------------------------------------------------------------------ + +Tables +============================================================================== + +Tables can be created using the following syntax: + +.. code-block:: none + + Colons can be used to align columns. + + | Tables | Are | Cool | + | ------------- |:-------------:| -----:| + | col 3 is | right-aligned | $1600 | + | col 2 is | centered | $12 | + | zebra stripes | are neat | $1 | + +.. raw:: html + :file: ../_static/html_snippets/markdown_example_table.html + +The outer pipes (|) are optional, and you don't need to make the raw Markdown +line up prettily, but there must be at least 3 dashes separating each header +cell. +You can also use inline Markdown. + +.. code-block:: none + + Markdown | Less | Pretty + --- | --- | --- + *Still* | `renders` | **nicely** + 1 | 2 | 3 + +.. raw:: html + :file: ../_static/html_snippets/markdown_example_table_2.html + +------------------------------------------------------------------------------ + +Video (Verto feature) +============================================================================== + +`Click here to read the documentation on how to include a video`_. + +------------------------------------------------------------------------------ + +.. _Verto: http://verto.readthedocs.io/en/latest/ +.. _submit a bug report: https://github.com/uccser/cs-unplugged/issues/new +.. _Click here to read the documentation on how to box text: http://verto.readthedocs.io/en/latest/processors/boxed-text.html +.. _list of language codes: https://haisum.github.io/2014/11/07/jekyll-pygments-supported-highlighters/ +.. _Click here to read the documentation on how to add a comment: http://verto.readthedocs.io/en/latest/processors/comment.html +.. _Click here to read the documentation on how to define a conditional: http://verto.readthedocs.io/en/latest/processors/conditional.html +.. _Click here to read the documentation on how to embed with an iframe: http://verto.readthedocs.io/en/latest/processors/iframe.html +.. _Click here to read the documentation on how to define a glossary link: http://verto.readthedocs.io/en/latest/processors/glossary-link.html +.. _Click here to read the documentation on how to create a heading: http://verto.readthedocs.io/en/latest/processors/heading.html +.. _Click here to read the documentation on how to include an image: http://verto.readthedocs.io/en/latest/processors/image.html +.. _Click here to read the documentation on how to include an interactive: http://verto.readthedocs.io/en/latest/processors/interactive.html +.. _Click here to read the documentation on how to create a relative link: http://verto.readthedocs.io/en/latest/processors/relative-link.html +.. _Click here to read the documentation on how to add a button link: http://verto.readthedocs.io/en/latest/processors/button-link.html +.. _Click here to read the documentation on how to create a panel: http://verto.readthedocs.io/en/latest/processors/panel.html +.. _Click here to read the documentation on how to include an image of Scratch block: http://verto.readthedocs.io/en/latest/processors/scratch.html +.. _Click here to read the documentation on how to include a table of contents: http://verto.readthedocs.io/en/latest/processors/table-of-contents.html +.. _Click here to read the documentation on how to include a video: http://verto.readthedocs.io/en/latest/processors/video.html diff --git a/docs/source/author/writing_philosophy.rst b/docs/source/author/writing_philosophy.rst new file mode 100644 index 000000000..3580f04c1 --- /dev/null +++ b/docs/source/author/writing_philosophy.rst @@ -0,0 +1,8 @@ +Writing Philosophy +############################################################################## + +This page covers the philosophy of writing material for CS Unplugged. + +Most of the philosophy of what we mean by the "Unplugged" approach is on our website (On the 'About' page under 'Principles'). + +In terms of how we present material, Unplugged activities are formatted as Lesson plans that have enough detail for a classroom to use, including links to computational thinking. If you have an idea for an activity, but it's not quite fleshed out enough as a lesson plan, it can be contributed as a PDF file to the Community section, which is an area where we collect great ideas that can inspire teachers and lesson plan authors. \ No newline at end of file diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst new file mode 100644 index 000000000..de155d962 --- /dev/null +++ b/docs/source/changelog.rst @@ -0,0 +1,72 @@ +Changelog +############################################################################## + +This page lists updates to CS Unplugged. +All notable changes to this project will be documented in this file. + +.. note :: + + We base our numbering system from the guidelines at `Semantic Versioning 2.0.0`_, + however since our project started before it was migrated to GitHub, the first + major open source release is being labeled as ``2.0.0``. + + Given a version number MAJOR.MINOR.HOTFIX: + + - MAJOR version change when major backend or text modifications are made + (for example: new topic). + - MINOR version change when content or functionality is added or updated (for + example: new videos, new activities, large number of text (typo/grammar) fixes). + - HOTFIX version change when bug hotfixes are made (for example: fixing a typo). + - A pre-release version is denoted by appending a hyphen and the alpha label + followed by the pre-release version. + + As this project contains text content, the updating of content doesn't perfectly + fit the Semantic Versioning model. However these version numbers can still + provide a good indication of the changes in each version. + +2.0.0-alpha.1 +============================================================================== + +- **Release date:** 20th June 2017 +- **Downloads:** `Source downloads are available on GitHub`_ + +**Notable changes:** + +The first major step in releasing a open source version of CS Unplugged. +While some existing content from the classic version of CS Unplugged have yet +to be adapted into the new format and system, we are releasing this version as +a sneak peek for teachers. + +The backend system contains the following features: + +- Open source system written in Django. + + - Allow translations of other languages (no translations are added yet). + - Deployable on Google App Engine, and easily customised for other hosts. + +- Website designed with Bootstrap 4 for use on all devices. +- Creates PDF resources for use with lessons. +- Basic test suite for checking system functionality. +- Documentation for the system. + +The following topics are available in this version: + +- Binary numbers: + + - 2 lessons for ages 5 to 7. + - 3 lessons for ages 8 to 11. + - 7 curriculum integrations. + - 24 programming challenges. + +- Error detetction and correction: + + - 2 lessons for ages 8 to 11. + - 5 curriculum integrations. + - 24 programming challenges. + +- Sorting networks: + + - 1 lesson for ages 8 to 10. + +.. _Semantic Versioning 2.0.0: http://semver.org/spec/v2.0.0.html +.. _Source downloads are available on GitHub: https://github.com/uccser/cs-unplugged/releases diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 000000000..1eb41f619 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +"""CS Unplugged documentation build configuration file. + +Created by sphinx-quickstart on Sun Feb 5 21:20:30 2017. + +This file is execfile()d with the current directory set to its +containing dir. + +Note that not all possible configuration values are present in this +autogenerated file. + +All configuration values have a default; values that are commented out +serve to show the default. + +If extensions (or modules to document with autodoc) are in another directory, +add these directories to sys.path here. If the directory is relative to the +documentation root, use os.path.abspath to make it absolute, like shown here. +""" + +import os +import sys +import sphinx_rtd_theme +sys.path.insert(0, os.path.abspath('../../')) +from csunplugged.config import __version__ + + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'CS Unplugged' +copyright = '2017 University of Canterbury Computer Science Education Research Group' +author = 'University of Canterbury Computer Science Education Research Group' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = __version__ +# The full version, including alpha/beta/rc tags. +release = __version__ + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = 'CSUnpluggeddoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, 'CSUnplugged.tex', 'CS Unplugged Documentation', + 'University of Canterbury Computer Science Education Research Group', 'manual'), +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, 'csunplugged', 'CS Unplugged Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, 'CSUnplugged', 'CS Unplugged Documentation', + author, 'CSUnplugged', 'One line description of project.', + 'Miscellaneous'), +] diff --git a/docs/source/developer/deployment.rst b/docs/source/developer/deployment.rst new file mode 100644 index 000000000..f7a3dddcb --- /dev/null +++ b/docs/source/developer/deployment.rst @@ -0,0 +1,132 @@ +Deployment +############################################################################## + +.. note:: + + This page is intended only for staff within the University of Canterbury + Computer Science Education Research Group, as deployment requires access + to secret passwords and values stored within our private password management + system. + +Requirements +============================================================================== + +The project is designed to run on the following systems: + +- Google App Engine: Flexible Enviroment. +- Google Cloud SQL: Postgres Database. + + - Several Django models require Postgres specific data types, so the + system will not function on a different database type (for example: MySQL). + +- Static files server. + +Deployment overview +============================================================================== + +Deployment requires three steps, and can be done in any order: + +1. Updating static files on Google Cloud Storage Bucket. +2. Updating Django application on Google App Engine. +3. Connecting to the Google Cloud SQL database and updating schema and data. + +We update in the order above to have minimal downtime between deploying a new +application and updating the database (currently around 30 to 60 seconds +on development deployment). + +Development deployment +============================================================================== + +Deployment of the ``develop`` branch occurs automatically with new commits to +the ``develop`` branch, and is deployed by Travis CI. + +The system is deployed to: http://cs-unplugged-develop.appspot.com/. + +The script and files used for deployment is stored in the +``infrastructure/develop-deploy/`` directory. +The ``develop-deploy.sh`` script contains descriptions of the process required +for deployment. +The file ``develop-deploy-secrets.tar.enc`` is an encrypted archive of files +containing sensitive data, and is decypted by Travis CI. + +Production deployment +============================================================================== + +There is currently no production deployment. + +Manual deployment +============================================================================== + +.. warning:: + + The following steps will allow manual deployment. + This information is not exhaustive, but will provide enough information for + manual deployment. + The ``develop-deploy.sh`` script has greater details of the deployment + process. + +For any manual deployment, you will require the `gcloud`_ tool to be +installed on your machine, and login with the research group admin Google +account. + +Manual deployment requires three steps, and can be done in any order: + +1. Updating static files on Google Cloud Storage Bucket. +2. Updating Django application on Google App Engine. +3. Connecting to the Google Cloud SQL database and updating schema and data. + +**Updating static files** + +The simpliest way to upload the static files on the storage bucket, is to +do the following: + +1. Delete the following folders if they exist: + + - ``csunplugged/build/`` + - ``csunplugged/staticfiles/`` + - ``csunplugged/temp/`` + + This may require administrator (``sudo``) permissions as these folders + are created by Docker. + +2. Run the ``./csu start`` command. + When the system is started, the static files are generated. + Once the system has started successfully, run the ``./csu end`` command. + +3. From the root directory, run the following command: + + .. code-block:: bash + + $ gsutil rsync -R csunplugged/staticfiles/ gs://cs-unplugged-develop/static/ + +**Updating Django application** + +In the ``infrastructure`` directory, create a copy of ``app-sample.yaml`` +called ``app.yaml`` in the root directory, and enter the required values. +The complete contents for this file is also stored in our password management +system. + +From the root directory, run the following command: + +.. code-block:: bash + + $ gcloud app deploy app.yaml + + +**Updating database** + +To update the development database, we will setup a SQL proxy to the server, +and then locally run the Django project that will connect to the server. +We can then perform the ``migrate`` and ``updatedata`` commands as required. + +Using the `guide for Django on Google App Engine Flexible Environment`_, +download and setup the SQL proxy. +You will need to choose a port that the SQL proxy and Django will operate on, +using ``5432`` should work for most developers. +Alter the Django configuration to connect using the proxy port, and run the +system. +You should be able to then perform the ``migrate`` and ``updatedata`` +commands. + +.. _gcloud: https://cloud.google.com/sdk/gcloud/ +.. _guide for Django on Google App Engine Flexible Environment: https://cloud.google.com/python/django/flexible-environment diff --git a/docs/source/developer/dev.rst b/docs/source/developer/dev.rst new file mode 100644 index 000000000..fd6a34ea5 --- /dev/null +++ b/docs/source/developer/dev.rst @@ -0,0 +1,54 @@ +Dev Application +############################################################################## + +This application: + +1. Provides a quick reference for viewing what content has been added to the database. +2. Allows authors to check Markdown content has rendered as expected. +3. Is only to be used in a development environment. + +This application is available only when ``DEBUG`` mode is enabled (and therefore is not +visible in production). ``DEBUG`` mode is enabled by default when developing locally. + +This application does not necessarily show the same connection/layout of content as in +the actual site itself. This is because some of the links in this app link to the actual +site, but many link to a copy of the template from the topics app (and are therefore are +likely to be out of date at any given point in time). + + +What does this app contain? +============================================================================== + +The dev application currently contains the following: + +- Topics - a list of available topics, and links to the corresponding template in the + topics applications. The children listed under each topic are: + + - Unit Plans and Lessons. - All of these link to the corresponding page in the Topics + application. The purpose of this list is to give an overview of which lessons have + been added to each unit plan, and which unit plans have been added to each topic. + It is possible for someone to have created a markdown file for a lesson (or unit + plan, or topic) but it not be shown in this list, this is because only Markdown + files listed in configuration (``yaml``) files are loaded into the database. + + - Curriculum Integrations - a list of curriculum integration activities. This **does + not** link to the template from the topic application. This is because a curriculum + integration only has to be linked to a topic (at the very least) in order for it to + be loaded into the database, i.e. it is possible for a curriculum integration to be + loaded into the database without it being assigned to a lesson. + + - Programming Challenges - a list of programming challenges. This **does not** link to + the template from the topic application. This is because a programming challenge + only has to be linked to a topic (at the very least) in order for it to be loaded + into the database, i.e. it is possible for a programming challenge to be loaded + into the database without it being assigned to a lesson. + +- Curriculum Areas - a list of curriculum areas and their subdomains. + +- Learning Outcomes - a list of learning outcomes. + +- Programming Challenge Languages - a list of programming languages that programming + challenge solutions can be given in. + +- Programming Challenge Difficulties - a list of difficulty levels and their corresponding + title. diff --git a/docs/source/developer/developer_philosophy.rst b/docs/source/developer/developer_philosophy.rst new file mode 100644 index 000000000..d7e2eb242 --- /dev/null +++ b/docs/source/developer/developer_philosophy.rst @@ -0,0 +1,33 @@ +Developer Philosophy +############################################################################## + +We follow the following philosophy for developing the CS Unplugged project: + + We aim to create software that enables authors to easily create, modify, + and share education material for the CS Unplugged project. + +In short: *Think of the author*. + +This page explains design decisions we made throughout developing the CS +Unplugged system. + +Configuration Files +============================================================================== + +We use `YAML`_ for storing configuration data of content. +It has improved human readability over JSON and XML, especially for authors +who have no or little experience with configuration files. + +We try to avoid deep nesting (indentation) within configuration files as it's +harder for authors to read nested data. +We have split configuration data across multiple configuration files to avoid +this issue. + +.. _YAML: http://www.yaml.org/spec/1.2/spec.html + +Writing Content +============================================================================== + +We chose Markdown as the language for text content as it has a great balance +between simplicity of writing for authors, and clear structure when converting +to HTML. diff --git a/docs/source/developer/django_setup.rst b/docs/source/developer/django_setup.rst new file mode 100644 index 000000000..6f886adf8 --- /dev/null +++ b/docs/source/developer/django_setup.rst @@ -0,0 +1,61 @@ +Django Setup +############################################################################## + +This page covers the configuration of Django for this project. + +Django Overview +============================================================================== + +We aim to create and clear Django system for ease of development. +We are using some advised patterns and practices from +`Two Scoops of Django - Best Practices for Django 1.8`_, which include (but is +not limited to the following): + +- Locking versions of dependencies. +- Django secret settings are loaded from environment variables. +- All templates are located in the ``templates/`` directory. +- The base Django directory, containing ``settings.py`` and base ``urls.py`` is + called ``config/``. + +The Django system currently contains the following applications: + +- ``general/`` - For general website pages (for example: home, about, etc). +- ``topics/`` - For topics content (for example: lessons, follow up activities, + programming challenges, etc). +- ``resources/`` - For translatable PDF resources with random elements. + +Database Structure +============================================================================== + +The following image shows the relationships between models across all +applications within the database. + +.. The following image can copied for be edits here: https://goo.gl/Vjv6XV +.. image:: ../_static/img/database_overview_diagram.png + :alt: A diagram detailing the general structure of the database + +.. note:: + + **Lesson** and **Programming Challenge Language Implementation** models have + the parent's **Topic** saved directly within their model. + +The rest of the :doc:`index` will inform you of how to develop the +applications and other components of the CS Unplugged project. + +Loaders +============================================================================== + +To populate the database with content we have written a series of custom loaders. +The loaders for an application are found in the ``management/commands/`` directory, and +there is approximately one loader for each configuration file. + +Besides populating fields in the database, a loader is also responsible for checking +that its corresponding configuration file contains all the required fields, Markdown files are +not empty, and icons can be found. If any of these conditions are not met, then an error +is thrown. + +Errors are defined in ``utils/errors/`` and should aim to be as descriptive and useful +as possible as they will most often be read by an author and not necessarily a Python +developer. + +.. _Two Scoops of Django - Best Practices for Django 1.8: https://www.twoscoopspress.com/products/two-scoops-of-django-1-8 diff --git a/docs/source/developer/general.rst b/docs/source/developer/general.rst new file mode 100644 index 000000000..af1ba9ffe --- /dev/null +++ b/docs/source/developer/general.rst @@ -0,0 +1,11 @@ +General Application +############################################################################## + +The general application manages and serves basic pages for the CS Unplugged +website, including (but not limited to): + +- Homepage +- About page +- Contact Us page + +Overal the ``general`` application is a standard Django application. diff --git a/docs/source/developer/index.rst b/docs/source/developer/index.rst new file mode 100644 index 000000000..723f3ed0a --- /dev/null +++ b/docs/source/developer/index.rst @@ -0,0 +1,18 @@ +Developer Documentation +############################################################################## + +The following pages are for those wanting to develop the CS Unplugged system. + +.. toctree:: + :maxdepth: 1 + :caption: Contents + + developer_philosophy + django_setup + website_design + general + topics + resources + dev + deployment + test_suite diff --git a/docs/source/developer/resources.rst b/docs/source/developer/resources.rst new file mode 100644 index 000000000..d47528539 --- /dev/null +++ b/docs/source/developer/resources.rst @@ -0,0 +1,11 @@ +Resources Application +############################################################################## + +.. warning:: + + The documentation for this page is yet to be written. + This page will cover: + + - Structure of application + - Structure of data/how resources are defined + - Creating new resources diff --git a/docs/source/developer/test_suite.rst b/docs/source/developer/test_suite.rst new file mode 100644 index 000000000..bda6f786f --- /dev/null +++ b/docs/source/developer/test_suite.rst @@ -0,0 +1,82 @@ +Test Suite +############################################################################## + +.. contents:: Contents + :local: + + +Running the Test Suite +============================================================================== + +``./csu dev test`` will run the entire test suite. For running a specific tests, viewing code coverage, and more testing commands, see :doc:`../getting_started/helper_commands`. + + +Structure +============================================================================== + +All tests are in the ``tests/`` directory, which in the ``csunplugged/`` directory (at the same level as the apps). It is structured as follows: + +.. code-block:: none + + └── tests/ + ├── general/ + │   ├── management/ + │   ├── templatetags/ + │   ├── urls/ + │   └── views/ + ├── resources/ + │   ├── loaders/ + │   ├── models/ + │   ├── resource/ + │   ├── urls/ + │   ├── views/ + │   └── ResourcesTestDataGenerator.py + ├── topics/ + │   ├── loaders/ + │   │   └── assets/ + │   ├── models/ + │   ├── urls/ + │   ├── views/ + │   └── TopicsTestDataGenerator.py + ├── utils + │ └── errors/ + └── BaseTestWithDB.py + +Note that each app being tested has it's own folder, and this is then broken down further into the component being tested (i.e. views, urls, models, etc). + +Items of interest from this diagram: + +- ``models/`` - contains a test file for each model in the app. + +- ``urls/`` - contains a test file for each url in the app. + +- ``views/`` - contains a test file for each view in the app. + +- ``loaders/`` - contains a test file for each loader in the app as well as an + ``assets/`` directory. Test yaml files should be saved in the corresponding loader directory within ``assets/`` and should mimick the folder structure of the app where necessary. + +- Test Data Generators - these are classes used to generate place holder data in + the database. You should use the methods in these files when testing a model that contains a foreign key and/or many-to-many field that needs to be + populated. + +- ``BaseTestWithDB.py`` - this class inherits the Django ``TestCase`` class, and + creates and logs in a user. This is the base test class that all other tests + should inherit from in order to interact with the database. + +- ``utils/errors/`` - contains test error classes for testing exceptions are + raised correctly by the loaders. + +Adding Tests +============================================================================== + +When writing a new test function, it is important that the method name is as +descriptive as possible. The method name should also be prefixed with ``test_`` +as the test suite will only execute methods with this prefix. + +.. note:: + + We use `Codecov`_ to check the coverage of our tests. Every Pull Request should + cover 100% of the difference (therefore increasing coverage), Travis will fail if this is not the case. + + +.. _Codecov: https://codecov.io/ diff --git a/docs/source/developer/topics.rst b/docs/source/developer/topics.rst new file mode 100644 index 000000000..fc9dc9540 --- /dev/null +++ b/docs/source/developer/topics.rst @@ -0,0 +1,27 @@ +Topics Application +############################################################################## + +The topics application is the main focus of the CS Unplugged website, as it +contains the majority of educational material for the project. + +.. note:: + + This guide assumes you've read the following documentation: + + - :doc:`../author/topics` - Provides detail on content in topics application + - :doc:`django_setup` - Provides detail of database structure + +In general the topics application is a standard Django application, however it +does store and update it's associated data for it's models uniquely. + +Instead of creating and updating model objects through the website +(for example: edited by an online editor), +the objects are updated by a management script. +The content for the topics application is stored within the ``contents/`` +directory, as is run for each deployment of the system. +Storing the content within the Git repository gives us greater control on +reviewing and accepted proposed changes to content. + +The management command for updating the applications data is ``loadtopics`` +(which is automatically called when running ``updatedata``), and can be found at +``management/commands/loadtopics.py``. diff --git a/docs/source/developer/website_design.rst b/docs/source/developer/website_design.rst new file mode 100644 index 000000000..314c55287 --- /dev/null +++ b/docs/source/developer/website_design.rst @@ -0,0 +1,40 @@ +Website Design (HTML templates/CSS) +############################################################################## + +This page covers the HTML templates and CSS styling used for the CS Unplugged +website. + +.. warning:: + + The current design of the website is a work in progress. + + Expect **everything** to change. + +In summary: + +- We plan to use Bootstrap 4 for the underlying framework for responsive design. +- We use SCSS for style sheets where possible. +- We use the `django-bootstrap-breadcrumbs`_ package for breadcrumbs on the + website. + + - This requires templates to inherit from the template one level up on the + breadcrumb track in order for breadcrumbs to be calculated correctly. + - This also requires specific objects in the context to be named the same + as parents in the breadcrumb chain. + For example: the associated topic is always saved in the template context + as ``topic``. + - For complete examples of the breadcrumb package being used, look at the + templates for the ``topics`` application. + +Setting Custom Converter Templates +============================================================================== +We use Verto to convert Markdown files to HTML. To override a default Verto +template, add a new HTML file to ``utils/custom_converter_templates/.html``. + +The template file name must correspond to the name of a processor in Verto +(for example: ``image.html``, or the name of a supporting template specified in +Verto documentation (for example: ``relative-image-link.html``). +A list of the available processors is available in the `Verto Documentation`_. + +.. _django-bootstrap-breadcrumbs: http://django-bootstrap-breadcrumbs.readthedocs.io/en/latest/ +.. _Verto Documentation: http://verto.readthedocs.io/en/master/processors/index.html#available-processos diff --git a/docs/source/getting_started/code_of_conduct.rst b/docs/source/getting_started/code_of_conduct.rst new file mode 100644 index 000000000..bcfbb2ad6 --- /dev/null +++ b/docs/source/getting_started/code_of_conduct.rst @@ -0,0 +1,83 @@ +Code of Conduct +############################################################################## + +Our Pledge +============================================================================== + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +Our Standards +============================================================================== + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention + or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +Our Responsibilities +============================================================================== + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +Scope +============================================================================== + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +Enforcement +============================================================================== + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at +```csse-education-research@canterbury.ac.nz``. +All complaints will be reviewed and investigated and will result in a response +that is deemed necessary and appropriate to the circumstances. The project +team is obligated to maintain confidentiality with regard to the reporter of +an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +Attribution +============================================================================== + +This Code of Conduct is adapted from the `Contributor Covenant`_, +`version 1.4`_. + +.. _version 1.4: http://contributor-covenant.org/version/1/4/ +.. _Contributor Covenant: http://contributor-covenant.org/ diff --git a/docs/source/getting_started/contributing_guide.rst b/docs/source/getting_started/contributing_guide.rst new file mode 100644 index 000000000..e6d716b41 --- /dev/null +++ b/docs/source/getting_started/contributing_guide.rst @@ -0,0 +1,234 @@ +Contributing Guide +############################################################################## + +This page lists a set of guidelines for contributing to the project. +These are just guidelines, not rules, use your best judgment and feel +free to propose changes to this document in a pull request. + +Reporting Issues and Making Suggestions +============================================================================== + +This section guides you through submitting an issue or making a suggestion +for the CS Unplugged project. +Following these guidelines helps maintainers and the community understand +your findings. + +Before Submitting an Issue +------------------------------------------------------------------------------ + +- `Search the issue tracker for the issue/suggestion`_ to see if it has + already been logged. + If it has, add a comment to the existing issue (even if the issue is closed) + instead of opening a new one. + +How do I Submit a Good Issue or Suggestion? +------------------------------------------------------------------------------ + +Issues are tracked in the GitHub issue tracker (if you've never used +GitHub issues before, read this `10 minute guide to become a master`_). +When creating an issue, explain the problem and include additional details to +help maintainers understand or reproduce the problem: + +For Reporting an Issue +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- **Use a clear and descriptive title** for the issue to identify the problem. +- **Clearly and concisely describe the issue** and provide screenshots if + required. +- **Link any related existing issues**. + +If the issues is a code related issue, also include the following: + +- **Describe the exact steps which reproduce the problem** in as many details + as possible. + For example, how you were generating a resource. + When listing steps, **don't just say what you did, explain how you did it**. +- **Explain which behavior you expected to see instead and why.** +- **Describe the behavior you observed after following the steps** and point + out what exactly is the problem with that behavior. +- **Can you reliably reproduce the issue?** If not, provide details about + how often the problem happens and under which conditions it normally happens. +- **Include screenshots or animated GIFs** if it helps explain the issue you + encountered. +- **What's the name and version of the OS you're using?** +- **What's the name and version of the browser you're using?** +- **If the problem is related to performance**, please provide + specifications of your computer. + +For Making a Suggestion +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Explain the suggestion and include additional details to help maintainers +understand the changes: + +- **Use a clear and descriptive title** for the issue to identify the + suggestion. +- **Clearly and concisely describe the suggestion** and provide screenshots if + required. +- **Explain why this suggestion would be useful** to most CS Unplugged users + and isn't something that should be a implemented as a community variant of + the project. +- **Link any related existing suggestions**. + +.. note:: + + **Internal Staff Only:** Assigning Issues + + Our policy is to only assign a person to an issue when they are actively + working on the issue. + Please don't assign yourself when you *plan* to do the task (for example: + in the next few days), assign yourself when you begin work. + This allows other team members to clearly see which tasks are available + to be worked on. + +Your First Code Contribution (pull request) +============================================================================== + +Unsure where to begin contributing to CS Unplugged? +You can start by looking through the `issue tracker`_. + +Pull Requests +------------------------------------------------------------------------------ + +- **Include a detailed explaination** of the proposed change, including + screenshots and animated GIFs in your pull request whenever possible. +- **Read and apply the style guides** listed below. +- Your pull request should be on a new branch from our ``develop`` branch, + **that is being requested to merge back into** ``develop``. + The naming conventions of branches should be descriptive of the new + addition/modification. + Ideally they would specify their namespace as well, for example: + + - ``resource/puzzle-town`` + - ``topic/algorithms`` + - ``issue/234`` + +- Link to any relevant existing issues/suggestions. +- Add necessary documentation (if appropriate). + +We aim to keep the CS Unplugged project as robust as possible, so please do +your best to ensure your changes won't break anything! + +Style and Etiquette Guides +============================================================================== + +Git +------------------------------------------------------------------------------ + +- Commits should be as descriptive as possible. + Other developers (and even future you) will thank you for your forethought + and verbosity for well documented commits. + Generally: + + - Limit the first line to 72 characters or less + - Reference issues and pull requests liberally + +- We use `Vincent Driessen's Git Branching Model `_ + for managing development. + Please read this document to understand our branching methods, and how + to perform clear branches and merges. + + Specifically for our respository: + + - We create a new branch for each task of work, no matter how small it is. + - We create the branch off the ``develop`` branch. + - In general, the new branch should begin with ``issue/`` followed by + the issue number. + - When a branch is completed, a pull request is created on GitHub for + review. + - Branches are merged back into ``develop``. + +GitHub +------------------------------------------------------------------------------ + +.. note:: + + Internal Staff Only + +- Mention a user (using the ``@`` symbol) when an issue is relevant to them. +- Only assign yourself to an issue, when you are actively working on it. +- The technical team may tag an author to review specific pull requests, and as + a reviewer you can either approve, request changes, or just leave comments. +- A pull request requires one review approval to be merged. +- If multiple people are tagged as reviewers, we only need one review (unless + otherwise specified). + For example: For content changes, we ask that at least one member from each + of the content and technical teams reviews the pull request. +- The creator of the pull request should assign all those suitable for review. +- The creator of the pull request is the only person who should merge the pull + request. + If you approve a pull request and it shows the big green button, please + resist clicking it! + +Project Structure +------------------------------------------------------------------------------ + +- Directories should be all lowercase with dashes for spaces. +- Directories and files should use full words when named, however JavaScript, + CSS, and image directories can be named ``js/``, ``css/``, and ``img/`` + respectively. + +Text (Markdown) +------------------------------------------------------------------------------ + +- Each sentence should be started on a newline (this greatly improves + readability when comparing two states of a document). + +Programming +------------------------------------------------------------------------------ + +Quote from Google style guides: + + Be consistent. + + If you’re editing code, take a few minutes to look at the code around you + and determine its style. + If they use spaces around all their arithmetic operators, you should too. + If their comments have little boxes of hash marks around them, make your + comments have little boxes of hash marks around them too. + + The point of having style guidelines is to have a common vocabulary of coding + so people can concentrate on what you’re saying rather than on how you’re + saying it. + We present global style rules here so people know the vocabulary, but local + style is also important. + If code you add to a file looks drastically different from the existing code + around it, it throws readers out of their rhythm when they go to read it. + Avoid this. + +We aim to abide by the following style guides: + +- **Python** - We follow `PEP8`_ except for one change of line length. + `Django recommends allowing 119 characters`_, so we use this as our line + length limit. + This style is enforced by the `flake8`_ style checker. +- **HTML** - We follow the `open source HTML style guide`_ by @mdo. +- **CSS** - We follow the `open source CSS style guide`_ by @mdo. +- **JavaScript** - We follow the `Google JavaScript style guide`_. + +Licencing +------------------------------------------------------------------------------ + +Any third-party libraries or packages used within this project should have +their listed within the ``LICENCE-THIRD-PARTY`` file, with a full copy of the +licence available within the ``third-party-licences`` directory. + +Final Comments +============================================================================== + +After reading the sections above, you should be able to answer the following +questions: + +- When do I create a issue and how do I describe it? +- When and how do I create a new Git branch to work on? +- *Internal staff only:* When do I assign myself to an issue? + +.. _Search the issue tracker for the issue/suggestion: https://github.com/uccser/cs-unplugged/issues?utf8=%E2%9C%93&q=is%3Aissue +.. _10 minute guide to become a master: https://guides.github.com/features/issues/ +.. _issue tracker: https://github.com/uccser/cs-unplugged/issues +.. _PEP8: https://www.python.org/dev/peps/pep-0008/ +.. _Django recommends allowing 119 characters: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/ +.. _open source HTML style guide: http://codeguide.co/#html +.. _open source CSS style guide: http://codeguide.co/#css +.. _Google JavaScript style guide: https://google.github.io/styleguide/javascriptguide.xml +.. _flake8: http://flake8.pycqa.org/en/latest/ diff --git a/docs/source/getting_started/helper_commands.rst b/docs/source/getting_started/helper_commands.rst new file mode 100644 index 000000000..21f6a0d40 --- /dev/null +++ b/docs/source/getting_started/helper_commands.rst @@ -0,0 +1,318 @@ +Helper Commands for Developing +############################################################################## + +.. note:: + + We assume by this point you have installed the project, checked the + setup is working (see :ref:`installation-check-project-setup-works`), + and also have a basic understanding of the + :doc:`project structure `. + +The CS Unplugged project uses many systems (Django, Docker, Gulp, etc) to run, +so we have written a script for running groups of commands for running the +system while developing. + +The script is called ``csu`` and can be found in the ``cs-unplugged`` folder +of the repository. +To run this script, open a terminal window in the directory and enter the +following command (you don't need to enter the ``$`` character, this shows +the start of your terminal prompt): + +.. code-block:: bash + + $ ./csu [COMMAND] + +Where ``[COMMAND]`` is a word from the list below: + +- :ref:`build` +- :ref:`clean` +- :ref:`dev` +- :ref:`end` +- :ref:`help` +- :ref:`restart` +- :ref:`start` +- :ref:`update` +- :ref:`wipe` + +All users of the project (content and technical developers) should become +familiar with the following commands: + +- :ref:`start` +- :ref:`end` +- :ref:`build` +- :ref:`update` + +Technical developers should also understand the ``dev`` command. + +----------------------------------------------------------------------------- + +.. _build: + +``build`` +============================================================================== + +Running ``./csu build`` will build or rebuild the Docker images that are +required for the CS Unplugged system. + +----------------------------------------------------------------------------- + +.. _clean: + +``clean`` +============================================================================== + +Running ``./csu clean`` deletes 'dangling' Docker images left over from builds, +which will free up hard drive space. + +----------------------------------------------------------------------------- + +.. _dev: + +``dev`` +============================================================================== + +The ``./csu dev [DEV_COMMAND]`` command runs developer tasks, where +``[DEV_COMMAND]`` is a word from the list below: + +- :ref:`logs` +- :ref:`flush` +- :ref:`makemigrations` +- :ref:`makeresources` +- :ref:`migrate` +- :ref:`shell` +- :ref:`static` +- :ref:`static_prod` +- :ref:`style` +- :ref:`test` +- :ref:`test_backwards` +- :ref:`test_coverage` +- :ref:`test_specific` +- :ref:`updatedata` + +.. _logs: + +``logs`` +----------------------------------------------------------------------------- + +Running ``./csu dev logs`` will display the logs for the running systems. +The output is for all logs until the time the command was run, therefore +successive calls may display new logs. + +To follow logs as they output, enter ``docker-compose logs --follow``. + +.. _flush: + +``flush`` +----------------------------------------------------------------------------- + +Running ``./csu dev flush`` runs the Django ``flush`` command to flush +the database. + +.. _makemigrations: + +``makemigrations`` +----------------------------------------------------------------------------- + +Running ``./csu dev makemigrations`` runs the Django ``makemigrations`` command +to create migration files. + +.. _makeresources: + +``makeresources`` +----------------------------------------------------------------------------- + +Running ``./csu dev makeresources`` runs the custom Django ``makeresources`` +command to create static resource PDF files. + +.. _migrate: + +``migrate`` +----------------------------------------------------------------------------- + +Running ``./csu dev migrate`` runs the Django ``migrate`` command +to apply migration files. + +.. _shell: + +``shell`` +----------------------------------------------------------------------------- + +Running ``./csu dev shell`` opens a bash terminal within the Django container +(this requires the CS Unplugged system to be running). + +This is the equivalent to entering ``docker-compose run django bash``. + +.. _static: + +``static`` +----------------------------------------------------------------------------- + +Running ``./csu dev static`` runs the commands for generating the static files +for the website. + +If changes are made to the static files (for example, a new image is added) +when the system is running, this command needs to be entered to view the +new files on the website. + +.. _static_prod: + +``static_prod`` +----------------------------------------------------------------------------- + +Running ``./csu dev static_prod`` runs the commands for generating production +static files for the website. +This produces compressed SASS files without sourcemaps. + +.. _style: + +``style`` +----------------------------------------------------------------------------- + +Running ``./csu dev style`` will run the ``flake8`` and ``pydocstyle`` commands +to check the style of the project. +If the output is ``0`` for a check, then there are zero errors. + +.. _test: + +``test`` +----------------------------------------------------------------------------- + +Running ``./csu dev test`` will run the test suite, and create a report +detailing test code coverage. +The code coverage report can be displayed by running +``./csu dev test_coverage``. + +.. _test_backwards: + +``test_backwards`` +----------------------------------------------------------------------------- + +Running ``./csu dev test_backwards`` will run the test suite in reverse. +This is useful to check if any tests are influencing the result of each other. +If this command if run on Travis CI, it will only run for a pull request. + +.. _test_coverage: + +``test_coverage`` +----------------------------------------------------------------------------- + +Running ``./csu dev test_coverage`` will display a table detailing test code +coverage, from the report generated by ``./csu dev test``. + +.. _test_specific: + +``test_specific`` +----------------------------------------------------------------------------- + +Running ``./csu dev test_specific [MODULE_PATH]`` will run a specific test +module. +For example, running +``./csu dev test_specific tests.resources.views.test_index_view`` will only +run the tests for checking the index view of the resources application. + +.. _updatedata: + +``updatedata`` +----------------------------------------------------------------------------- + +Running ``./csu dev updatedata`` runs the custom ``updatedata`` command to +load the topics content into the database. + +----------------------------------------------------------------------------- + +.. _end: + +``end`` +============================================================================== + +Running ``./csu end`` will stop any containers which are currently running, +this usually takes 10 to 20 seconds. + +----------------------------------------------------------------------------- + +.. _help: + +``help`` +============================================================================== + +Running ``./csu help`` displays brief help text for the script. +More details for each command can be found on this page. + +----------------------------------------------------------------------------- + +.. _restart: + +``restart`` +============================================================================== + +Running ``./csu restart`` is a shortcut for running: + +- ``./csu end`` +- ``./csu start`` + +More details for each command can be found on this page. + +----------------------------------------------------------------------------- + +.. _start: + +``start`` +============================================================================== + +Running ``./csu start`` starts the development environment. +When you run this command for the first time on a computer it will also run +``./csu build`` to build the system Docker images. +This can take some time, roughly 15 to 30 minutes, depending on your computer +and internet speed. +Images are only required to be built once, unless the image specifications +change (you can rebuild the images with ``./csu build``). +Once the images are built, the script will run these images in containers. + +Once the development environment is operational, the script will perform the +following tasks: + +- Start the Django website system +- Start the Nginx server to display the website and static files +- Start the database server +- Update the database with the required structure (known as the schema) +- Load the CS Unplugged content into the database +- Create the required static files + +Once the script has performed all these tasks, the script will let you know +the website is ready. +Open your preferred web browser to the URL ``localhost`` to view the website. + +----------------------------------------------------------------------------- + +.. _update: + +``update`` +============================================================================== + +Running ``./csu update`` runs the Django ``makemigratations`` and ``migrate`` +commands for updating the database schema, and then runs the custom +``updatedata`` command to load the topics content into the database. +It also runs the ``static`` command to generate static files. + +If changes are made to the topics content when the system is running, this +command needs to be run to view the new changes on the website. + +----------------------------------------------------------------------------- + +.. _wipe: + +``wipe`` +============================================================================== + +Running ``./csu wipe`` delete all Docker containers and images on your computer. +Once this command has be run, a full download and rebuild of images is +required to run the system (can be triggered by the ``build`` or ``start`` +commands). + +----------------------------------------------------------------------------- + +You now know the basic commands for using the CS Unplugged system. +You are now ready to tackle the documentation for the area you wish to +contribute on. +Head back to the :doc:`documentation homepage <../index>` and choose the documentation related +to the task you wish to contribute to. diff --git a/docs/source/getting_started/index.rst b/docs/source/getting_started/index.rst new file mode 100644 index 000000000..bde7fbdc8 --- /dev/null +++ b/docs/source/getting_started/index.rst @@ -0,0 +1,27 @@ +Getting Started +############################################################################## + +This documentation will help you understand how the project is setup, +basic steps on how to use it, and our guidelines for your contributions. + +.. note:: + + This project adheres to the Contributor Covenant code of conduct. + By participating, you are expected to uphold this code. + Please read our :doc:`Code of Conduct ` before continuing. + You can report unacceptable behaviour by + `emailing us `_. + +.. toctree:: + :hidden: + + code_of_conduct + +.. toctree:: + :maxdepth: 1 + :caption: Contents + + contributing_guide + installation + project_structure + helper_commands diff --git a/docs/source/getting_started/installation.rst b/docs/source/getting_started/installation.rst new file mode 100644 index 000000000..e5f338ef0 --- /dev/null +++ b/docs/source/getting_started/installation.rst @@ -0,0 +1,262 @@ +Installation Guide +################################################# + +This page will set your machine up for working on the CS Unplugged project. +You should only need to do these installation steps once (unless the required +steps for setup change). + +Requirements +================================================= + +- At least 5 GB of hard drive space. +- An internet connection to download 1 to 2 GB of data. + +Recommended Reading +================================================= + +If you aren't familiar with the following systems, we recommend +reading tutorials first on how to use them: + +- Entering terminal commands for your operating system +- Git (here are two Git tutorials: `one`_ `two`_) + +Step 1: Setup Virtual Machine (optional) +================================================= + +For those working on a computer in a restricted environment (for example: +a computer managed by an education insitution), then working in a +**virtual machine** is recommended. + +If you wish to setup a virtual machine for development, we have a guide here: +:doc:`../other/setup_virtual_machine`. + +.. _step-2-install-git: + +Step 2: Install Git +================================================= + +Install the version control software `Git`_ onto your computer. + +.. note:: + + If you are new to Git and not comfortable with using the terminal, + you may like to use a free program like `SourceTree`_ to use Git. + +Step 3: Create GitHub Account +================================================= + +If you don't already have an account on GitHub, create a free account on +the `GitHub website`_. +This account will be tied to any changes you submit to the project. + +Step 4: Set Git Account Values +================================================= + +When you make a commit in Git (the term for changes to the project), the +commit is tied to a name and email address. We need to set name and email +address within the Git system installed on the machine. + +- `Setting your username in Git`_ +- `Setting your email in Git`_ + +You can also `keep your email address private on GitHub`_ if needed. + +.. note:: + + If your GitHub account is secured with two-factor authentication (2FA) + this is a perfect time to setup `SSH keys`_. + +Step 5: Download the CS Unplugged Repository +================================================= + +Firstly create the directory you wish to hold the CS Unplugged repository +directory in if you wish to store the data in a specific location. +Once you have decided upon the location, clone (the Git term for download) the +project onto your computer. + +If you are using terminal commands to use Git, type the following command in +terminal (you don't need to enter the ``$`` character, this shows the start of +your terminal prompt): + +.. code-block:: bash + + $ git clone https://github.com/uccser/cs-unplugged.git + +.. note:: + + If you connect to GitHub through SSH, then type: + + .. code-block:: bash + + $ git clone git@github.com:uccser/cs-unplugged.git + +Once Git has cloned the directory, checkout the repository to the development +branch ``develop``. + +Step 6: Install Docker +================================================= + +We use a system called `Docker`_ to run the CS Unplugged system, both on local +machine for development, and also when deployed to production. +Download the latest version of the free Docker Community Edition for your +operating system from the `Docker Store`_. + +Once you have installed the software, run the following commands in a terminal +to check Docker is working as intended (you don't need to enter the ``$`` +character, this shows the start of your terminal prompt). + +.. code-block:: bash + + $ docker version + $ docker-compose version + $ docker run hello-world + +.. note:: + + Depending on your operating system, if the above commands don't work you + may need to set Docker to be able to run without ``sudo``. + You will need to do this in order to use the ``csu`` helper script. + +Step 7: Install Text Editor/IDE (optional) +================================================= + +This is a good time to install your preferred IDE or text editor, if you don't +have one already. +Some free options we love: + +- `Atom`_ +- `Sublime Text`_ + +Step 8: Install Developer Tools (optional) +================================================= + +.. note:: + + You can skip this step if you're only adding content to the project. + +For those developing the CS Unplugged system, you will need to install some +tools on your computer for local development. +These tools include packages for style checking and compiling documentation. + +Install Python 3 +------------------------------------------------------------------------------ + +Install Python 3 with the following command in terminal: + +.. code-block:: bash + + $ sudo apt install python3 + +Install Python 3 PIP +------------------------------------------------------------------------------ + +Then install Python 3 pip (pip is a package management system used to +install and manage software packages written in Python) with the following +command in terminal: + +.. code-block:: bash + + $ sudo apt install python3-pip + +Install Python virtualenv +------------------------------------------------------------------------------ + +We recommend (though it's not required) to work within a virtual environment +(see :ref:`what-is-a-virtual-environment`). +This helps to prevent conflicts with dependencies. + +Install virtualenv with the following command in terminal: + +.. code-block:: bash + + $ sudo pip3 install virtualenv + +.. note:: + + **Optional step:** You can also install `virtualenvwrapper`_ to make it + easier when using and managing your virtual environments. + +Create Virtual Environment +------------------------------------------------------------------------------ + +Type the following commands in terminal to create and activate +a virtualenv named ``venv``. +You can change the virtual environment name to whatever you wish. +You will need to replace the ``x`` with the version number of Python you +have (for example: ``python3.5``): + +.. code-block:: bash + + $ python -m virtualenv --python=python3.x venv + $ . venv/bin/activate + +.. note:: + + If you installed ``virtualenvwrapper``, then type the following command to + to create a virtual environment called ``csunplugged``, with Python within + the virtual environment already set to Python 3. + + .. code-block:: bash + + $ mkvirtualenv --python=/usr/bin/python3.x csunplugged + +You should now have the name of your virtual environment before the terminal +prompt. + +Install Packages into the Virtual Environemnt +------------------------------------------------------------------------------ + +Now that the virtual environment is active, we can install the Python packages +into it for local development. +This allows you to run these tools without having to run these within the +Docker system. + +.. code-block:: bash + + $ pip install -r requirements/local.txt + +.. _installation-check-project-setup-works: + +Step 9: Check Project Setup Works +================================================= + +To check the project works, open a terminal in the project root directory, +which is the ``cs-unplugged/`` directory (should contain a file called +``csu``). + +Type the following command into the terminal (we will cover this command +in more detail on the next page): + +.. code-block:: bash + + $ ./csu start + +If this is the first time you're running this script, it will need to build +system images. +This can take some time, roughly 15 to 30 minutes, depending on your computer +and internet speed (we recommend grabbing a cup of tea and watching an episode +of Brooklyn Nine-Nine on Netflix). + +After the helper script builds the system images, it will automatically start +the system, and will let you know when the system is ready. +You should then be able to open your preferred web browser to the URL +``localhost`` and see the CS Unplugged homepage. + +Congratulations if you made it this far and everything is working, +you're all set to contribute to the CS Unplugged project. + +.. _one: https://git-scm.com/docs/gittutorial +.. _two: https://try.github.io/levels/1/challenges/1 +.. _virtualenvwrapper: https://virtualenvwrapper.readthedocs.io/en/latest/ +.. _Git: https://git-scm.com/ +.. _SourceTree: https://www.sourcetreeapp.com/ +.. _GitHub website: https://github.com/ +.. _SSH keys: https://help.github.com/articles/connecting-to-github-with-ssh/ +.. _Setting your username in Git: https://help.github.com/articles/setting-your-username-in-git/ +.. _Setting your email in Git: https://help.github.com/articles/setting-your-email-in-git/ +.. _keep your email address private on GitHub: https://help.github.com/articles/keeping-your-email-address-private/ +.. _Docker: https://www.docker.com/ +.. _Docker Store: https://store.docker.com/search?type=edition&offering=community +.. _Verto documentation: http://verto.readthedocs.io/en/latest/install.html +.. _Atom: https://atom.io/ +.. _Sublime Text: https://www.sublimetext.com/ diff --git a/docs/source/getting_started/project_structure.rst b/docs/source/getting_started/project_structure.rst new file mode 100644 index 000000000..7bf2a0dc0 --- /dev/null +++ b/docs/source/getting_started/project_structure.rst @@ -0,0 +1,117 @@ +Project Structure +########################################### + +This page covers the structure of the CS Unplugged project. +The following diagram will be helpful when reading the following sections: + +.. raw:: html + :file: ../_static/html_snippets/project_directory_tree.html + +Repository Directory +================================================= + +The repository directory (or root directory) contains the following: + +- ``csunplugged/`` + + - This directory contains the Django web system for the CS Unplugged website. + This includes all raw text content, images, resources, etc. + +- ``docs/`` + + - This directory contains the documentation for the repository (which includes + the file you are reading now). + +- ``subtitles/`` + + - This directory contains subtitle files for CS Unplugged videos. + +- ``README.md`` + + - This file contains an introduction and important information for the + repository. + +- ``LICENCE.md`` + + - This file details the licences the repository uses. + +- Plus other files used for installation and repository configuration. + +csunplugged Directory +================================================= + +The ``csunplugged/`` directory holds the Django web system and is split across +the following directories: + +- ``config/`` + + - This directory holds the settings used by the Django system. + It's unlikely you'll edit the contents of this directory unless you are + changing the Django configuration (for example: adding a new application). + +.. _django-applications: + +Django contains 'applications' which are Python packages that provide +some set of features. +Each large part/chunk of the CS Unplugged is a separate application. +The project currently contains the following applications: + +- ``general/`` + + - This applicate displays webpages for generic pages on the website. + For example: homepage, about page, contact page, etc. + +- ``topics/`` + + - The core CS Unplugged content is split across topics, with each topic + containing any combination of unit plans, lessons, follow up activities, + programming challenges (plugged in activities), and much more. + This application stores and displays the topics content. + +- ``resources/`` + + - The CS Unplugged project contains custom printable resources that can + contain randomly generated components and translations. + This application stores, generates, and displays these resources. + +Details on how to modify an application can be found within their relavent +author and developer documentation pages. + +The following directories are also required by the Django system: + +- ``static/`` + + - This directory contains non-user-generated media assets (for example: + images, JavaScript, CSS/SCSS, etc). + +- ``templates/`` + + - This directory contains all the HTML templates for the Django system. + +- ``locale/`` + + - This directory contains translations required for the Django system. + Translations for ``topics/`` are stored within the ``topics/content/``. + +The following directories are used when the server is running (for example: +a script compiles the SCSS to CSS and saves it to the ``build/`` directory for +serving on a webpage). +You should never save anything in these directories, as the contents are often +overwritten and cleared. + +- ``build/`` + + - Contains the generated output of the front-end script (for example: + compiled and minified CSS and JavaScript, compressed images, etc). + +- ``temp/`` + + - Contains temporary files used in creating generated files for + ``build`` directory. + +The ``csunplugged/`` directory also contains the following files: + +- ``manage.py`` + + - A file created by Django used to manage the Django web system. + Don't modify the contents of this file. diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 000000000..434ec8b2c --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,61 @@ +Welcome to CS Unplugged +############################################################################## + +Welcome to the documentation for the CS Unplugged project. +The documentation is split into the following sections: + +------------------------------------------------------------------------------ + +:doc:`Getting Started Documentation ` +============================================================================== + +This documentation contains: + +- Our expectations of your contributions to the project +- Steps to install the project on your system +- Explainations on how the project is setup +- Details on basic commands to use the system + +------------------------------------------------------------------------------ + +:doc:`Author Documentation ` +============================================================================== + +This documentation is for those who want to help write content for the CS +Unplugged project, specifically text content. +This is the documentation to read if you want to do any of the +following (or similar): + +- Add a new topic +- Clear up instructions on a lesson +- Add a language implementation for a programming challenge +- Add a translation + +------------------------------------------------------------------------------ + +:doc:`Developer Documentation ` +============================================================================== + +This documentation is for those who want to contribute to developing the +Django system for delivering the CS Unplugged content. +This is the documentation to read if you want to do any of the +following (or similar): + +- Edit HTML templates used to render webpages +- Edit CSS used for styling the website +- Add a generatable PDF resource +- Contribute to test suite for checking database models +- Alter URL routing for views + +------------------------------------------------------------------------------ + +.. toctree:: + :maxdepth: 4 + :caption: Table of Contents + + getting_started/index + author/index + developer/index + other/index + troubleshooting + changelog diff --git a/docs/source/other/faq.rst b/docs/source/other/faq.rst new file mode 100644 index 000000000..cb12f8fbb --- /dev/null +++ b/docs/source/other/faq.rst @@ -0,0 +1,72 @@ +Frequently Asked Questions +############################################################################## + +The topics application is the main focus of the CS Unplugged website, as it +contains the majority of educational material for the project. + +.. contents:: Contents + :local: + +.. _what-is-a-key: + +What is a Key? +============================================================================== + +We use the term "key" to specify a field name. +Keys map to particular values (which range from learning outcome text, to the +structure and attributes of a lesson). + +A key is a short label for something, containing only letters, numbers, +or hyphens. +In our system, a key must be no longer than 50 characters, and use hyphens +instead of underscores. + +These are *valid* examples of keys: + +- ``algorithms`` +- ``binary-numbers`` +- ``challenge-2`` + +These are *invalid* examples of keys: + +- ``Algorithms`` +- ``Binary Numbers`` +- ``Binary_Numbers`` +- ``binary_numbers`` +- ``challenge 2`` + +Keys must be exact matches to work, for example, if you name a lesson +``bits-and-bytes``, referencing it in another configuration file as +``bytes-and-bits`` will raise an error. + +See also: + +- `Definition of URL slug on Wikipedia`_ + +.. _what-is-an-application: + +What is an Application? +============================================================================== + +Django contains 'applications' which are Python packages that provide +some set of features. +Each large part/chunk of the CS Unplugged is a separate application. +Read :ref:`this section in our project structure guide ` +for details of the applications used in the CS Unplugged system. + +.. _what-is-a-virtual-environment: + +What is a Virtual Environment? +============================================================================== + +A Virtual Environment is a tool to keep the dependencies required by different +projects in separate places, by creating virtual Python environments for them. +It solves the “Project X depends on version 1.x but, Project Y needs 4.x” +dilemma, and keeps your global site-packages directory clean and manageable. + +See also: + +- `Guide on Virtual Environments`_ + +.. _Definition of URL slug on Wikipedia: https://en.wikipedia.org/wiki/Semantic_URL#Slug +.. _Guide on Virtual Environments: http://docs.python-guide.org/en/latest/dev/virtualenvs/ diff --git a/docs/source/other/index.rst b/docs/source/other/index.rst new file mode 100644 index 000000000..9ac75a0c8 --- /dev/null +++ b/docs/source/other/index.rst @@ -0,0 +1,9 @@ +Other Documentation +############################################################################## + +.. toctree:: + :maxdepth: 1 + :caption: Contents + + faq + setup_virtual_machine diff --git a/docs/source/other/setup_virtual_machine.rst b/docs/source/other/setup_virtual_machine.rst new file mode 100644 index 000000000..37add5d6c --- /dev/null +++ b/docs/source/other/setup_virtual_machine.rst @@ -0,0 +1,51 @@ +Setting up a Virtual Machine +================================================= + +The following steps are just one way to set up an Linux virtual machine +for CS Unplugged development, and are intended for those new to setting up +virtual machines. + +1. Download and install `Oracle VirtualBox`_. +2. Download installation of preferred Linux operating system. + A common choice is `Ubuntu 16.04.2 LTS`_. +3. Open VirtualBox and click ``New``. +4. Enter a name for your virtual machine, for example "CS Unplugged". + Also select the type and version of your operating system. + If you downloaded the Ubuntu linked above, the type is "Linux", + and version is "Ubuntu (64-bit)". Click "Next/Continue". +5. Choose the memory you wish to allocate (2048 MB to 4096 MB is recommended). + Click "Next/Continue". +6. Select "Create a virtual hard disk now". Click "Create". +7. Keep the default selection of "VDI (VirtualBox Disk Image)". + Click "Next/Continue". +8. Keep the default selection of "Dynamically allocated". + Click "Next/Continue". +9. Change the name and location of the hard drive for the virtual + machine if required (default is usually fine). +10. Select the maximum size that the hard drive can grow to (be aware of + how much space you have available on your machine). + We recommend 20 GB, as this is enough space to store the Ubuntu operating + system and the CS Unplugged project. + Click "Create". +11. Select your virtual machine by clicking on it, and click "Settings" above. + Within these settings you can also setup a shared clipboard between the + host and virtual machines, plus increase the number of processors the + virtual machine can use. There are plenty of guides available online for + how to enable them. +12. Click the "Storage" category. Under "Controller: IDE" click "Empty". + On the right of Optical Drive dropdown menu, click the CD disk icon. + Select "Choose Virtual Optical Disk File" and select the operating system + installation file that you downloaded earlier (it's probably in your + "Downloads" folder). + Close the Settings window. +13. Select the virtual machine by clicking on it and click the green "Start" + button above. + The operating system installation screen should appear upon starting. +14. Install the operating system using the default settings. +15. Once the operating system has completed installation, you are ready to use + it for developing the CS Unplugged project. + You're now ready for :ref:`step-2-install-git` in the + installation guide. + +.. _Oracle VirtualBox: https://www.virtualbox.org/ +.. _Ubuntu 16.04.2 LTS: https://www.ubuntu.com/download/desktop diff --git a/docs/source/troubleshooting.rst b/docs/source/troubleshooting.rst new file mode 100644 index 000000000..2748a0588 --- /dev/null +++ b/docs/source/troubleshooting.rst @@ -0,0 +1,77 @@ +Troubleshooting +############################################################################## + +``csu`` helper script +============================================================================== + +I get a ``No such file or directory`` error when running the ``csu`` script +------------------------------------------------------------------------------ + +Check your terminal working directory is within the ``cs-unplugged`` directory, +the root directory of the project. +Running the ``ls`` command in this directory should list the ``csu`` file. + +I have an error when running ``./csu start`` +------------------------------------------------------------------------------ + +If you are having issues running the ``start`` command, try rebuilding the +system images with ``./csu build``. +Changes may be have been made to the system images since you initally created +them. + +If this still doesn't solve your problem, you could also try deleting any +existing images with ``./csu wipe``, and then build and start the system with +``./csu start``. + +If issue still persists, log a bug on our `issue tracker`_. + +Viewing website +============================================================================== + +Images are not displayed when I view the website +------------------------------------------------------------------------------ + +Firstly check the image is located in the ``staticfiles/`` directory. +If the image isn't located within the directory, check the original image is +located within the ``static`` directory. + +**Normal Images (not Scratch block images)** + +If the image is located within the ``static/`` directory, check the +filepath is correct. +When running ``./csu update``, the script will report an error if an image +cannot be found. + +**Scratch block images** + +Check the ``.txt`` files located within the ``temp/`` directory for a file +containing the Scratch block syntax for the image missing (project wide +find & replace is your friend here). + +If you can find the file with the same block syntax and there isn't a image +in the ``staticfiles/`` directory with the same filename +(``.svg`` instead of ``.txt``), try running ``./csu update``. +If this doesn't fix the problem, the problem is in our scripts so log a +bug on our `issue tracker`_. + +If you can't find a file within the ``temp/`` directory, check the syntax used +in the text is valid. +If the syntax is valid, the problem is in our Markdown to HTML converter to +log a bug on our `issue tracker`_. + +Changed CSS/SCSS styles are not updated when I view the website +------------------------------------------------------------------------------ + +Firstly check the compiled CSS is located in the ``staticfiles/`` directory, +and that the changes have appeared in the compiled CSS file. +If the CSS file does not include the changes made, run ``./csu static``. +If you are editing SCSS files, check the ``./csu static`` command isn't reporting +SCSS compilation errors. + +The website isn't displaying when I open ``localhost`` in a browser +---------------------------------------------------------------------------------------------- + +Check you have run the ``./csu start`` command, and that it hasn't reported any +errors. + +.. _issue tracker: https://github.com/uccser/cs-unplugged/issues diff --git a/infrastructure/app-sample.yaml b/infrastructure/app-sample.yaml new file mode 100644 index 000000000..4a333106e --- /dev/null +++ b/infrastructure/app-sample.yaml @@ -0,0 +1,12 @@ +runtime: custom +env: flex + +beta_settings: + cloud_sql_instances: "SET ME!" + +env_variables: + DJANGO_SECRET_KEY: "SET ME!" + GOOGLE_CLOUD_SQL_DATABASE_USERNAME: "SET ME!" + GOOGLE_CLOUD_SQL_DATABASE_PASSWORD: "SET ME!" + GOOGLE_CLOUD_SQL_CONNECTION_NAME: "SET ME!" + GOOGLE_CLOUD_STORAGE_BUCKET_NAME: "SET ME!" diff --git a/infrastructure/dev-deploy/dev-deploy-secrets.tar.enc b/infrastructure/dev-deploy/dev-deploy-secrets.tar.enc new file mode 100644 index 000000000..2bee1812d Binary files /dev/null and b/infrastructure/dev-deploy/dev-deploy-secrets.tar.enc differ diff --git a/infrastructure/dev-deploy/dev-deploy.sh b/infrastructure/dev-deploy/dev-deploy.sh new file mode 100755 index 000000000..b52931e9f --- /dev/null +++ b/infrastructure/dev-deploy/dev-deploy.sh @@ -0,0 +1,129 @@ +#!/bin/bash +# Deploy the system to the development website. + +# Generate production static files +./csu dev static_prod + +# Generate static PDF resources for deployment. +./csu dev makeresources + +# Install Python requirements for running Django locally when using Google +# Cloud SQL proxy. Ideally this would be run inside the Docker image, to +# prevent having to reinstall the requirements again. +# +# Travis is running on Python 3.6, as set in '.travis.yml'. +# +# Travis already uses isolated virtualenvs to no need to create a virtualenv. +# See: https://docs.travis-ci.com/user/languages/python/#Travis-CI-Uses-Isolated-virtualenvs +pip install -r ./requirements/production.txt + +# Set the environment variable for Google Cloud SDK to disable prompts +# and choose default settings. +export CLOUDSDK_CORE_DISABLE_PROMPTS=1; + +# Install base Google Cloud SDK to use gcloud command tool. +curl https://sdk.cloud.google.com | bash; + +# Decrypt secret files archive that contain credentials. +# +# This includes: +# - continuous-deployment-dev.json +# Google Cloud Platform Service Account for using with gcloud. +# - app-dev.yaml +# Google App Engine YAML file for deployment, contains sensitive data. +# - load-dev-deploy-envs.sh +# Loads environment variables used when running local Django. +openssl aes-256-cbc -K "${encrypted_323d8adec5b7_key}" -iv "${encrypted_323d8adec5b7_iv}" -in ./infrastructure/dev-deploy/dev-deploy-secrets.tar.enc -out dev-deploy-secrets.tar -d + +# Unzip the decrypted secret archive into the current folder. +tar -xf dev-deploy-secrets.tar + +# Authenticate with gcloud tool using the decrypted service account credentials. +# See: https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account +gcloud auth activate-service-account --key-file continuous-deployment-dev.json + +# Peform an update of gcloud to latest version. +# This installs the 'app' tool, for deploying to Google App Engine. +# See: https://cloud.google.com/sdk/gcloud/reference/components/update +sudo gcloud components update + +# Display the gcloud version, useful for debugging purposes. +# See: https://cloud.google.com/sdk/gcloud/reference/version +gcloud version + +# Create empty SSH keys with an empty passphrase, for Google Cloud SDK to +# copy files to a VM for building the Docker image. +# Only required for deploying to Google App Engine flexible environment. +# See: https://cloud.google.com/solutions/continuous-delivery-with-travis-ci#continuous_deployment_on_app_engine_flexible_environment_instances +ssh-keygen -q -N "" -f ~/.ssh/google_compute_engine + +# Delete all previous stopped versions of application on Google App Engine. +# This command deletes all stopped applications of the 'default' service, +# and when the new application is deployed, the previous application version +# will remain until the next deployment. +# We delete old versions due avoid hitting the App Engine version limit. +declare -a versions_to_delete +versions_to_delete=($(gcloud app versions list --filter="SERVING_STATUS=STOPPED" --service=default --format="[no-heading]" | awk '{print $2}' | tr '\n' ' ')) +for version in "${versions_to_delete[@]}"; do + gcloud app versions delete --service=default ${version} +done + +# Load environment variables. +# Used when running local Django for updating development database. +. ./load-dev-deploy-envs.sh + +# Download the Google Cloud SQL proxy for updating development database. +# +# This is done before any deployment to minimise downtime between the app +# deployment and the database update. +# See: https://cloud.google.com/python/django/flexible-environment#install_the_sql_proxy +wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 +# Rename the proxy to standard filename. +mv cloud_sql_proxy.linux.amd64 cloud_sql_proxy +# Allow the proxy to be executable. +chmod +x cloud_sql_proxy + +# Start the Google Cloud SQL Proxy. +# +# This connects to the database instance using the connection name set in an +# environment variable. It is authenticated using the service account credentials. +# The proxy command is appended with '&>/dev/null &' to run in the background +# and to not send output to console. +# See: https://cloud.google.com/python/django/flexible-environment#initialize_your_cloud_sql_instance +./cloud_sql_proxy -instances="${GOOGLE_CLOUD_SQL_CONNECTION_NAME}"=tcp:5433 -credential_file="./continuous-deployment-dev.json" >/dev/null 2>/dev/null & + +# Publish static files. +# +# This copies the generated static files from tests to the Google Storage +# Bucket. +# See: https://cloud.google.com/python/django/flexible-environment#deploy_the_app_to_the_app_engine_flexible_environment +gsutil rsync -R ./csunplugged/staticfiles/ gs://cs-unplugged-dev.appspot.com/static/ + +# Publish Django system to Google App Engine. +# +# This deploys using the 'app-develop.yaml' decrypted earlier that contains +# secret environment variables to use within the application. +# Project is specified to ensure correct project deployment. +# Runs with '--quiet' to skip prompt of confirmation. +# If multiple services are deployed at a later stage, these should be checked +# that the apps deploy to the correct services. +# See: https://cloud.google.com/sdk/gcloud/reference/app/deploy +gcloud app deploy ./app-dev.yaml --quiet --project=cs-unplugged-dev + +# Update development database. +# +# The approach used for updating the database is to run the Django system +# locally and connect to the development database using the Google Cloud SQL +# proxy. +# +# Change working directory to 'csunplugged' to run 'manage.py' from the +# expected location for running loaders. +cd csunplugged +# Run the 'migrate' command to update the database schema, if required. +# Runs with the database_proxy settings which connect the database to the +# Google Cloud SQL proxy. +python ./manage.py migrate --settings=config.settings.database_proxy +# Run the 'updatedata' command to update the database content. +# Runs with the database_proxy settings which connect the database to the +# Google Cloud SQL proxy. +python ./manage.py updatedata --settings=config.settings.database_proxy diff --git a/infrastructure/nginx/Dockerfile b/infrastructure/nginx/Dockerfile new file mode 100644 index 000000000..789a02f3b --- /dev/null +++ b/infrastructure/nginx/Dockerfile @@ -0,0 +1,37 @@ +FROM nginx:latest + +# Populate sources with closest neighbours +# Resolves issues with failing builds, should be removed in future +RUN apt-get update \ + && apt-get install -y netselect-apt --no-install-suggests \ + && netselect-apt stretch \ + && mv sources.list /etc/apt/sources.list + +# Install Node.js and other dependencies +RUN apt-get update \ + && apt-get install --no-install-suggests -y \ + curl \ + gnupg2 \ + python \ + build-essential \ + && curl -sL https://deb.nodesource.com/setup_6.x | bash - \ + && apt-get install --no-install-suggests -y \ + nodejs \ + && apt-get remove --purge -y gnupg2 && apt-get -y --purge autoremove \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app +RUN npm install --global gulp-cli node-gyp +RUN apt-get update \ + && apt-get install --no-install-suggests -y \ + libcairo2-dev \ + libjpeg-dev \ + libpango1.0-dev \ + libgif-dev \ + g++ \ + && rm -rf /var/lib/apt/lists/* + +ADD csunplugged/package.json /app/ +RUN npm install +ADD ./csunplugged/ /app/ +ADD infrastructure/nginx/nginx.conf /etc/nginx/nginx.conf diff --git a/infrastructure/nginx/nginx.conf b/infrastructure/nginx/nginx.conf new file mode 100644 index 000000000..1b1862045 --- /dev/null +++ b/infrastructure/nginx/nginx.conf @@ -0,0 +1,31 @@ +worker_processes 1; + +events { + worker_connections 1024; +} + +http { + include mime.types; + server { + listen 80; + server_name example.org; + + access_log /dev/stdout; + error_log /dev/stdout info; + + location / { + # checks for static file, if not found proxy to app + try_files $uri @proxy_to_app; + expires -1; + alias /app/; + } + + # Gunicorn/Django container + location @proxy_to_app { + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $http_host; + proxy_redirect off; + proxy_pass http://django:8080; + } + } +} diff --git a/infrastructure/prod-deploy/prod-deploy-secrets.tar.enc b/infrastructure/prod-deploy/prod-deploy-secrets.tar.enc new file mode 100644 index 000000000..79db080aa Binary files /dev/null and b/infrastructure/prod-deploy/prod-deploy-secrets.tar.enc differ diff --git a/infrastructure/prod-deploy/prod-deploy.sh b/infrastructure/prod-deploy/prod-deploy.sh new file mode 100644 index 000000000..0a1891a08 --- /dev/null +++ b/infrastructure/prod-deploy/prod-deploy.sh @@ -0,0 +1,129 @@ +#!/bin/bash +# Deploy the system to the production website. + +# Generate production static files +./csu dev static_prod + +# Generate static PDF resources for deployment. +./csu dev makeresources + +# Install Python requirements for running Django locally when using Google +# Cloud SQL proxy. Ideally this would be run inside the Docker image, to +# prevent having to reinstall the requirements again. +# +# Travis is running on Python 3.6, as set in '.travis.yml'. +# +# Travis already uses isolated virtualenvs to no need to create a virtualenv. +# See: https://docs.travis-ci.com/user/languages/python/#Travis-CI-Uses-Isolated-virtualenvs +pip install -r ./requirements/production.txt + +# Set the environment variable for Google Cloud SDK to disable prompts +# and choose default settings. +export CLOUDSDK_CORE_DISABLE_PROMPTS=1; + +# Install base Google Cloud SDK to use gcloud command tool. +curl https://sdk.cloud.google.com | bash; + +# Decrypt secret files archive that contain credentials. +# +# This includes: +# - continuous-deployment-prod.json +# Google Cloud Platform Service Account for using with gcloud. +# - app-prod.yaml +# Google App Engine YAML file for deployment, contains sensitive data. +# - load-prod-deploy-envs.sh +# Loads environment variables used when running local Django. +openssl aes-256-cbc -K "${encrypted_9cabeeff4658_key}" -iv "${encrypted_9cabeeff4658_iv}" -in ./infrastructure/prod-deploy/prod-deploy-secrets.tar.enc -out prod-deploy-secrets.tar -d + +# Unzip the decrypted secret archive into the current folder. +tar -xf prod-deploy-secrets.tar + +# Authenticate with gcloud tool using the decrypted service account credentials. +# See: https://cloud.google.com/sdk/gcloud/reference/auth/activate-service-account +gcloud auth activate-service-account --key-file continuous-deployment-prod.json + +# Peform an update of gcloud to latest version. +# This installs the 'app' tool, for deploying to Google App Engine. +# See: https://cloud.google.com/sdk/gcloud/reference/components/update +sudo gcloud components update + +# Display the gcloud version, useful for debugging purposes. +# See: https://cloud.google.com/sdk/gcloud/reference/version +gcloud version + +# Create empty SSH keys with an empty passphrase, for Google Cloud SDK to +# copy files to a VM for building the Docker image. +# Only required for deploying to Google App Engine flexible environment. +# See: https://cloud.google.com/solutions/continuous-delivery-with-travis-ci#continuous_deployment_on_app_engine_flexible_environment_instances +ssh-keygen -q -N "" -f ~/.ssh/google_compute_engine + +# Delete all previous stopped versions of application on Google App Engine. +# This command deletes all stopped applications of the 'default' service, +# and when the new application is deployed, the previous application version +# will remain until the next deployment. +# We delete old versions due avoid hitting the App Engine version limit. +declare -a versions_to_delete +versions_to_delete=($(gcloud app versions list --filter="SERVING_STATUS=STOPPED" --service=default --format="[no-heading]" | awk '{print $2}' | tr '\n' ' ')) +for version in "${versions_to_delete[@]}"; do + gcloud app versions delete --service=default ${version} +done + +# Load environment variables. +# Used when running local Django for updating production database. +. ./load-prod-deploy-envs.sh + +# Download the Google Cloud SQL proxy for updating production database. +# +# This is done before any deployment to minimise downtime between the app +# deployment and the database update. +# See: https://cloud.google.com/python/django/flexible-environment#install_the_sql_proxy +wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 +# Rename the proxy to standard filename. +mv cloud_sql_proxy.linux.amd64 cloud_sql_proxy +# Allow the proxy to be executable. +chmod +x cloud_sql_proxy + +# Start the Google Cloud SQL Proxy. +# +# This connects to the database instance using the connection name set in an +# environment variable. It is authenticated using the service account credentials. +# The proxy command is appended with '&>/dev/null &' to run in the background +# and to not send output to console. +# See: https://cloud.google.com/python/django/flexible-environment#initialize_your_cloud_sql_instance +./cloud_sql_proxy -instances="${GOOGLE_CLOUD_SQL_CONNECTION_NAME}"=tcp:5433 -credential_file="./continuous-deployment-prod.json" >/dev/null 2>/dev/null & + +# Publish static files. +# +# This copies the generated static files from tests to the Google Storage +# Bucket. +# See: https://cloud.google.com/python/django/flexible-environment#deploy_the_app_to_the_app_engine_flexible_environment +gsutil rsync -R ./csunplugged/staticfiles/ gs://cs-unplugged.appspot.com/static/ + +# Publish Django system to Google App Engine. +# +# This deploys using the 'app-prod.yaml' decrypted earlier that contains +# secret environment variables to use within the application. +# Project is specified to ensure correct project deployment. +# Runs with '--quiet' to skip prompt of confirmation. +# If multiple services are deployed at a later stage, these should be checked +# that the apps deploy to the correct services. +# See: https://cloud.google.com/sdk/gcloud/reference/app/deploy +gcloud app deploy ./app-prod.yaml --quiet --project=cs-unplugged + +# Update production database. +# +# The approach used for updating the database is to run the Django system +# locally and connect to the production database using the Google Cloud SQL +# proxy. +# +# Change working directory to 'csunplugged' to run 'manage.py' from the +# expected location for running loaders. +cd csunplugged +# Run the 'migrate' command to update the database schema, if required. +# Runs with the database_proxy settings which connect the database to the +# Google Cloud SQL proxy. +python ./manage.py migrate --settings=config.settings.database_proxy +# Run the 'updatedata' command to update the database content. +# Runs with the database_proxy settings which connect the database to the +# Google Cloud SQL proxy. +python ./manage.py updatedata --settings=config.settings.database_proxy diff --git a/requirements/base.txt b/requirements/base.txt new file mode 100644 index 000000000..3d24e7541 --- /dev/null +++ b/requirements/base.txt @@ -0,0 +1,31 @@ +# Wheel 0.25+ needed to install certain packages on CPython 3.5+ +# like Pillow and psycopg2 +# See http://bitly.com/wheel-building-fails-CPython-35 +# Verified bug on Python 3.5.1 +wheel==0.29.0 + +# Django +django==1.11.2 + +# Configuration +django-environ==0.4.3 + +# Images +Pillow==4.1.1 + +# Markdown +verto==0.6.1 +python-markdown-math==0.3 + +# Python-PostgreSQL Database Adapter +psycopg2==2.7.1 + +# Templates +django-bootstrap-breadcrumbs==0.8.2 + +# YAML Loading +PyYAML==3.12 + +# Web serving +gevent==1.2.2 +gunicorn==19.7.1 diff --git a/requirements/local.txt b/requirements/local.txt new file mode 100644 index 000000000..66fbfe18f --- /dev/null +++ b/requirements/local.txt @@ -0,0 +1,11 @@ +# Local development dependencies go here +-r base.txt +-r test.txt + +# Documentation +sphinx==1.6.2 +sphinx-rtd-theme==0.2.4 + +# Debugging Tools +django-debug-toolbar==1.8 +django-extensions==1.7.9 diff --git a/requirements/production.txt b/requirements/production.txt new file mode 100644 index 000000000..0f8e51271 --- /dev/null +++ b/requirements/production.txt @@ -0,0 +1,4 @@ +# Pro-tip: Try not to put anything here. +# Avoid dependencies in production that +# aren't in development. +-r base.txt diff --git a/requirements/test.txt b/requirements/test.txt new file mode 100644 index 000000000..b48861868 --- /dev/null +++ b/requirements/test.txt @@ -0,0 +1,8 @@ +# Test dependencies + +# Check Python style +flake8==3.3.0 +pydocstyle==2.0.0 + +#Coverage Tools +coverage==4.4.1 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 000000000..aa9a672ef --- /dev/null +++ b/setup.cfg @@ -0,0 +1,30 @@ +[flake8] +exclude = + # No need to traverse our git or venv directories + .git, + venv, + docker_venv, + + # There's no value in checking cache directories + __pycache__, + + # These files are mostly or completely autogenerated + migrations, + manage.py, + csunplugged/manage.py, + + # This contains our built project files and documentation + build, + + # This contains node_modules that we don't want to check + node_modules, + +show-source = True +statistics = True +count = True +max-line-length = 119 + +[pydocstyle] +# Ignore following rules to allow Google Python Style docstrings +add_ignore = D407,D413 +match_dir = (?!migrations|node_modules).* diff --git a/subtitles/csu_parity_magic_trick.vtt b/subtitles/csu_parity_magic_trick.vtt new file mode 100644 index 000000000..f90b43b71 --- /dev/null +++ b/subtitles/csu_parity_magic_trick.vtt @@ -0,0 +1,597 @@ +WEBVTT + +NOTE +Computer Science Education Research, +University of Canterbury, New Zealand +Subtitle file for the video "CS Unplugged: Parity Magic" +Author: Alasdair Smith +Date: 13/02/2017 + +00:00.000 --> 00:04.000 +CS Unplugged +Computer Science without a computer + +00:04.600 --> 00:06.200 +Parity Magic + +00:06.200 --> 00:10.000 +Cool so, hands up who thinks +computers are a little bit magical, + +00:10.000 --> 00:13.600 +they do stuff and we're not too sure how they do it. + +00:13.600 --> 00:18.400 +Does anyone think they're a little bit magical +or have thought they have been in the past? Cool. + +00:18.400 --> 00:21.600 +Well I'm going to teach you a magic trick today. + +00:21.600 --> 00:26.400 +So, what I need is a volunteer +who can make a five by five grid + +00:26.400 --> 00:32.800 +with these magnetic cards. So it could be white, +or it could be black and it has to be totally random. + +00:32.800 --> 00:36.200 +Who would like to do that? +Oh thanks Ellie, you come up? + +00:36.200 --> 00:39.200 +You do that. +Hopefully that's enough cards there, cool. + +00:39.200 --> 00:42.000 +If you could just make a five by five +grid up here that's great. + +00:42.000 --> 00:45.000 +Now while she's doing that I want +to explain a little bit + +00:45.000 --> 00:51.200 +about what's happening. So, I said before +that this is one bit; each of these magnets represents + +00:51.200 --> 00:57.800 +one bit. A bit can be turned off, +or a bit can be turned on. + +00:57.800 --> 01:04.200 +Where else have we learned about bits before? +Turn to the person next to you and tell them about that. + +NOTE +[silence] + +01:18.000 --> 01:20.800 +And eyes this way. + +01:20.800 --> 01:25.000 +So bits, did anyone mention in their whispering, + +01:25.000 --> 01:30.600 +did anyone mention the words Binary Numbers +when we learned about Binary Numbers the other day? + +NOTE changed 'word' to 'words' in the above sentence + +01:30.600 --> 01:34.600 +Yeah? So these are bits, and these could +be like the Binary Numbers right? + +01:34.600 --> 01:37.200 +They could be on, +or they could be off. + +01:37.200 --> 01:40.000 +Cool has she got five by five? + +01:40.000 --> 01:45.400 +Yeah? Cool thanks Ellie, so I'm going to make +this magic trick just a little bit harder for myself, + +01:45.400 --> 01:51.200 +and I'm going to put an extra column down here, + +01:51.200 --> 01:57.800 +and I'm going to put an extra row under here, + +01:57.800 --> 02:00.600 +just to make it hard. + +02:06.600 --> 02:09.000 +Okay + +02:10.400 --> 02:13.400 +Right I've done that, now, + +02:13.400 --> 02:19.200 +I've got a really bad memory, +and I can't see very well when I take off my glasses. + +02:19.200 --> 02:21.600 +Ooh, I can't see at all now. + +02:21.600 --> 02:25.600 +I'm going to ask someone to go up +and turn one of them over, + +02:25.600 --> 02:29.200 +hands up who thinks I'd be able to guess where it is? + +02:29.200 --> 02:31.600 +Hands up who thinks that's impossible +if you've got a bad memory, + +02:31.600 --> 02:35.200 +or is even impossible +if you've got a good memory? Hands up? + +02:35.200 --> 02:38.000 +Okay, let's see, +Rhylee can you go up and turn one over? + +02:38.000 --> 02:41.000 +Everyone else you need to check +either that he is turning it over + +02:41.000 --> 02:42.800 +or you need to check that I'm not cheating, + +02:42.800 --> 02:47.000 +and I'm not looking. +I'll take my glasses off to be even sure. + +02:49.200 --> 02:51.800 +Everyone say "done" when he's done it. + +02:51.800 --> 02:53.000 +["Done"] + +02:53.000 --> 02:59.000 +Right, Hmm, Rhylee Rhylee Rhylee. + +03:03.400 --> 03:05.400 +Right or wrong? + +03:05.400 --> 03:06.600 +["Right"] + +03:06.600 --> 03:09.000 +Was it a fluke, it could have been a fluke right? + +03:09.000 --> 03:12.200 +Thanks Rhylee let's give him a big hand. Thanks Rhylee. + +03:12.200 --> 03:15.800 +Okay, who thinks they can trick me, +maybe because it was on the bottom row? + +03:15.800 --> 03:18.600 +I'd memorised the bottom row? Maybe maybe maybe. + +03:18.600 --> 03:22.200 +Sammy, I'm going over here this time +so I'm definitely not cheating, + +03:22.200 --> 03:26.200 +you guys need to watch if Sammy's turned one over +and some of you need to check that I'm not cheating + +03:26.200 --> 03:32.000 +and I'll even turn off the eyes in the back of my head +just to be sure that's not happening. + +03:34.800 --> 03:36.000 +["Done"] + +03:36.000 --> 03:41.000 +Right let's have a look. Sammy Sammy Sammy. + +03:45.400 --> 03:47.000 +Right or wrong? + +03:47.000 --> 03:50.200 +[Confused chorus] + +03:50.200 --> 03:52.600 +Was I right? Awesome! + +03:52.600 --> 03:56.800 +So if I get it right two times is it a magic trick? + +03:56.800 --> 04:01.000 +Or is something else going on? +Hands up if you still think it's still a magic trick? + +04:01.000 --> 04:04.200 +Hands up if you think there must be something going on. + +04:04.200 --> 04:09.000 +Cool, let's look at what is going on. + +04:10.600 --> 04:16.800 +If we had these just as we had them before +and we go through the rows, + +04:16.800 --> 04:22.000 +how many black cards can you see in this row? + +04:22.000 --> 04:23.400 +["Four"] + +04:23.400 --> 04:25.600 +And how many black cards in this one? + +04:25.600 --> 04:33.000 +["Four"] <04:27.400> ["Four"] <04:29.200> ["Two"] +<04:30.800> ["Four"] <04:32.400> ["Four"] + +04:33.000 --> 04:38.200 +Okay, now let's look at these columns, +how many are in this column? + +NOTE changed 'how many is in' to 'how many are in' + +04:38.200 --> 04:48.000 +["Two"] <04:40.400> ["Six"] <04:42.200> ["Four"] +<04:43.600> ["Two"] <04:45.000> ["Four"] <04:46.600> ["Four"] + +04:48.000 --> 04:52.000 +Hmm, what do those numbers all have in common? + +04:52.000 --> 04:56.000 +What are they? How would we classify those numbers? +Yes? + +04:56.000 --> 04:57.400 +Even Numbers? + +04:57.400 --> 05:00.000 +Hmm, hands up if you agree with that statement? + +05:00.000 --> 05:03.000 +They're even numbers and you are right. + +05:03.000 --> 05:11.000 +You are right because I said I was going to make it +harder by adding the extra column and row right? + +05:11.000 --> 05:12.800 +Did I make it harder or easier? + +05:12.800 --> 05:14.000 +["Easier"] + +05:14.000 --> 05:20.000 +How did I make it easier when I +added a black card here onto this row? + +NOTE Changed 'Why' to 'How' + +05:20.000 --> 05:22.000 +What was I doing? + +05:22.000 --> 05:28.000 +Um, You were making it as, so, +add one more so you could memorise them easily? + +05:28.000 --> 05:31.600 +Yeah? Well if I didn't do that +how many black cards are there now? + +05:31.600 --> 05:32.800 +["Three"] + +05:32.800 --> 05:36.600 +When I add a black card there what am I making it? Taking it from odd to? + +05:36.600 --> 05:42.400 +["Even"] Even right, so actually +by adding that column and adding that row, + +05:42.400 --> 05:50.200 +I made all of the black cards even, +so I was able to correct the mistake, correct the error + +05:50.200 --> 05:53.400 +which is the one that got flipped over. + +05:53.400 --> 05:59.200 +So let's see how that worked. +I'm going to actually take away the extra column, + +05:59.200 --> 06:02.800 +and the extra row, +and you're going to tell me what to put. + +06:02.800 --> 06:08.600 +Now, I am also going to rub out the numbers +so you've got no prompts,<06:07.04> okay? + +06:08.600 --> 06:11.000 +Are you ready? +I want you to call out black or white + +06:11.000 --> 06:13.800 +and tell me what I have to do here. +What do I have to do here? + +06:13.800 --> 06:18.800 +["Black"] <06:15.600> ["Black"] +<06:17.200>[Confused chorus] + +06:18.800 --> 06:22.400 +Black + +06:22.400 --> 06:27.200 +["White"] <06:25.200> ["Black"] + +06:27.200 --> 06:30.600 +Alright, now, all those rows are done, let's do the columns. + +06:30.600 --> 06:37.800 +["Black"] <06:32.200> ["Black"] +<06:34.400> ["Black"] <06:36.200> ["White"] + +06:37.800 --> 06:40.200 +Oops, sorry + +06:40.200 --> 06:43.600 +["Black"] <06:42.200> ["White"] + +06:43.600 --> 06:47.200 +Okay I want you to see what's happening here, + +06:47.200 --> 06:53.400 +because if I put it white there: +Does that make that column correct? + +06:53.400 --> 06:54.600 +["Yes"] + +06:54.600 --> 06:58.000 +Does it make that row correct? +<06:57.000> ["Yes"] + +06:58.000 --> 07:04.000 +So that corner there, it actually is putting +two of the rows and the columns into place, + +07:04.000 --> 07:07.600 +so it's quite an important card isn't it? + +07:07.600 --> 07:15.600 +Okay. So, now that you've seen how it works, +who thinks they would like to have a go at doing this? + +07:15.600 --> 07:18.600 +Who thinks they'd like to have a go? +Robert, do you think you can do it? + +07:18.600 --> 07:21.800 +Okay, so you're going to stand over there. +Close your eyes, turn off the eyes + +07:21.800 --> 07:24.400 +in the back of your head, power them down. + +07:24.400 --> 07:28.800 +And I need someone who's going +to come up and change it. + +07:28.800 --> 07:35.400 +Jack, come up and change it for us. Change one, +you guys look and check that he's just changing one, + +07:35.400 --> 07:40.600 +I'm going to check that +Robert's not going to turn around. + +07:49.000 --> 07:51.000 +Cool, what are we going to tell Robert? +["Done"] + +07:51.000 --> 07:54.200 +Okay, Robert come over here +and we're just going to help you with this. + +07:54.200 --> 07:57.400 +So, this row here, how many are there? + +07:57.400 --> 07:59.000 +Four +Is that okay? Yes + +07:59.000 --> 08:02.200 +What about this one? Four +Is that okay? Yes + +08:02.200 --> 08:04.800 +What about this one? Four +Is that okay? Yes + +08:04.800 --> 08:07.400 +What about this one? Two +Is that okay? Yes + +08:07.400 --> 08:10.400 +What about this one? +<08:08.400>Three, so there must be a problem. + +08:10.400 --> 08:14.600 +Ahahahahaa! Let's put a circle around there, + +08:14.600 --> 08:18.600 +because I think you're right. +I think there is a problem, because it's an odd number. + +08:18.600 --> 08:21.400 +Do we have to check the next row? +<08:20.600>["No"] + +08:21.400 --> 08:24.400 +Why not? +<08:22.04>Because we're already found the answer. + +08:24.400 --> 08:28.800 +We've already found the answer and we know +there's only one error to detect right? + +08:28.800 --> 08:32.000 +So now let's look at the columns: +How many are in here? + +NOTE changed 'is' to 'are' + +08:32.000 --> 08:32.800 +Two + +08:32.800 --> 08:34.000 +Is that alright? +Yes + +08:34.000 --> 08:36.800 +What about this one? Six +Is that okay? Yes + +08:36.800 --> 08:39.600 +What about this one? Four +Is that okay? Yes + +08:39.600 --> 08:43.600 +What about this one? +One, so there must be detected problem. + +08:43.600 --> 08:46.400 +There is a problem in there, let's have a look at that. + +08:46.400 --> 08:49.400 +So, which card was flipped Robert? + +08:49.400 --> 08:53.600 +This one, because there's a line around there +and a line around there. + +08:53.600 --> 08:57.000 +So it already gives me +a direct spot to where it is. + +08:57.000 --> 08:59.400 +Turn it over good work! + +08:59.400 --> 09:02.800 +Okay hands up if you agree with Robert +that that was the one that was changed? + +09:02.800 --> 09:11.400 +Cool let's give him a hand well done, cool. +<09:07.600>Thanks Robert. + +09:11.400 --> 09:15.200 +So, why are we learning this with +a whole lot of magnets on the board + +09:15.200 --> 09:17.500 +and how does this relate back to the computers? + +09:17.500 --> 09:21.200 +Because, what we've actually got here +is a representation. + +09:21.200 --> 09:28.000 +This here that Ellie put up, this is the data +for a piece of music she's written, + +09:28.000 --> 09:32.000 +and she's now uploading that up to the internet. + +09:32.000 --> 09:36.000 +Now, if she's doing that, +she's probably using phone wires and things, + +09:36.000 --> 09:41.600 +and so what we're doing is just adding +a little bit more data to her file here, + +09:41.600 --> 09:46.300 +and her data, and what that's doing, +is if there's any interference; + +09:46.300 --> 09:53.400 +if for some reason +a signal is off rather than on, + +09:53.400 --> 09:59.400 +it means that all of this together can be +corrected in the computer and it stays the same + +09:59.400 --> 10:02.200 +for the person who's receiving it at the other end. + +10:02.200 --> 10:07.200 +And that's the same with your CDs and DVDs, +so hands up if you've ever had a DVD + +10:07.200 --> 10:12.000 +where it's starting to glitch, +it's started to get pixelated or the sound's not working? + +10:12.000 --> 10:20.600 +Right so, hand's down, if you turn your DVD over +you'll see lots and lots of shiny and dull, + +10:20.600 --> 10:26.200 +things on it? Each of those is a bit, and it holds +billions of bits on your DVD. + +10:26.200 --> 10:31.000 +And what happens, is if you get a scratch on it, +or if you get one fleck of dust, + +10:31.000 --> 10:35.400 +if you didn't have this extra data, +your DVD would be of no use, + +10:35.400 --> 10:37.000 +you'd have to throw it away. + +10:37.000 --> 10:43.400 +But, the DVD has this extra data on it, +and so if one fleck of dust gets on, + +10:43.400 --> 10:45.600 +it can cope with that it'll just correct it. + +10:45.600 --> 10:50.200 +And it has to be quite a decent scratch +before it starts to pixelate. + +10:50.200 --> 10:53.600 +Have you seen the scratches? +Have you taken the DVD out, looked at it and gone + +10:53.600 --> 10:57.000 +"Woah, that's really dirty and scratched. +That must be why it's not working." + +10:57.000 --> 10:58.400 +Have you ever done that? + +10:58.400 --> 11:02.600 +Cool, and you clean it, +and it works again right hopefully? + +11:02.600 --> 11:05.800 +And that's because you have gotten it to a state again + +11:05.800 --> 11:09.800 +that it can now read and it can correct +all the errors and the detections + +11:09.800 --> 11:13.600 +Cool, thanks. diff --git a/third-party-licences/bootstrap.txt b/third-party-licences/bootstrap.txt new file mode 100644 index 000000000..1381cb2be --- /dev/null +++ b/third-party-licences/bootstrap.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2011-2017 Twitter, Inc. +Copyright (c) 2011-2017 The Bootstrap Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/third-party-licences/coverage.txt b/third-party-licences/coverage.txt new file mode 100644 index 000000000..f433b1a53 --- /dev/null +++ b/third-party-licences/coverage.txt @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/third-party-licences/details-element-polyfill.txt b/third-party-licences/details-element-polyfill.txt new file mode 100644 index 000000000..b3345a9bc --- /dev/null +++ b/third-party-licences/details-element-polyfill.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Javan Makhmali + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/third-party-licences/django-bootstrap-breadcrumbs.txt b/third-party-licences/django-bootstrap-breadcrumbs.txt new file mode 100644 index 000000000..fdfc7db9b --- /dev/null +++ b/third-party-licences/django-bootstrap-breadcrumbs.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Łukasz Mierzwa + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/third-party-licences/django-debug-toolbar.txt b/third-party-licences/django-debug-toolbar.txt new file mode 100644 index 000000000..221d73313 --- /dev/null +++ b/third-party-licences/django-debug-toolbar.txt @@ -0,0 +1,27 @@ +Copyright (c) Rob Hudson and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of Django nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third-party-licences/django-environ.txt b/third-party-licences/django-environ.txt new file mode 100644 index 000000000..e60a44b79 --- /dev/null +++ b/third-party-licences/django-environ.txt @@ -0,0 +1,19 @@ +Copyright (c) 2013-2017, Daniele Faraglia + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/third-party-licences/django-extensions.txt b/third-party-licences/django-extensions.txt new file mode 100644 index 000000000..279c6daa9 --- /dev/null +++ b/third-party-licences/django-extensions.txt @@ -0,0 +1,19 @@ +Copyright (c) 2007 Michael Trier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/third-party-licences/django.txt b/third-party-licences/django.txt new file mode 100644 index 000000000..5f4f225dd --- /dev/null +++ b/third-party-licences/django.txt @@ -0,0 +1,27 @@ +Copyright (c) Django Software Foundation and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of Django nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third-party-licences/flake8.txt b/third-party-licences/flake8.txt new file mode 100644 index 000000000..e5e3d6f94 --- /dev/null +++ b/third-party-licences/flake8.txt @@ -0,0 +1,22 @@ +== Flake8 License (MIT) == + +Copyright (C) 2011-2013 Tarek Ziade +Copyright (C) 2012-2016 Ian Cordasco + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third-party-licences/gevent.txt b/third-party-licences/gevent.txt new file mode 100644 index 000000000..b767c2450 --- /dev/null +++ b/third-party-licences/gevent.txt @@ -0,0 +1,25 @@ +MIT License + +Except when otherwise stated (look at the beginning of each file) the software +and the documentation in this project are copyrighted by: + + Denis Bilenko and the contributors, http://www.gevent.org + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/third-party-licences/gunicorn.txt b/third-party-licences/gunicorn.txt new file mode 100644 index 000000000..040d78a69 --- /dev/null +++ b/third-party-licences/gunicorn.txt @@ -0,0 +1,23 @@ +2009-2017 (c) Benoît Chesneau +2009-2015 (c) Paul J. Davis + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/third-party-licences/jquery.txt b/third-party-licences/jquery.txt new file mode 100644 index 000000000..e4e5e00ef --- /dev/null +++ b/third-party-licences/jquery.txt @@ -0,0 +1,36 @@ +Copyright JS Foundation and other contributors, https://js.foundation/ + +This software consists of voluntary contributions made by many +individuals. For exact contribution history, see the revision history +available at https://github.com/jquery/jquery + +The following license applies to all parts of this software except as +documented below: + +==== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +==== + +All files located in the node_modules and external directories are +externally maintained libraries used by this software which have their +own licenses; we recommend you read them, as their terms may differ from +the terms above. diff --git a/third-party-licences/noto-sans.txt b/third-party-licences/noto-sans.txt new file mode 100644 index 000000000..d952d62c0 --- /dev/null +++ b/third-party-licences/noto-sans.txt @@ -0,0 +1,92 @@ +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/third-party-licences/patrick-hand.txt b/third-party-licences/patrick-hand.txt new file mode 100644 index 000000000..04869a1e5 --- /dev/null +++ b/third-party-licences/patrick-hand.txt @@ -0,0 +1,86 @@ +SIL OPEN FONT LICENSE + +Version 1.1 - 26 February 2007 + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting — in part or in whole — any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/third-party-licences/pillow.txt b/third-party-licences/pillow.txt new file mode 100644 index 000000000..f8346793e --- /dev/null +++ b/third-party-licences/pillow.txt @@ -0,0 +1,16 @@ +The Python Imaging Library (PIL) is + + Copyright © 1997-2011 by Secret Labs AB + Copyright © 1995-2011 by Fredrik Lundh + +Pillow is the friendly PIL fork. It is + + Copyright © 2010-2017 by Alex Clark and contributors + +Like PIL, Pillow is licensed under the open source PIL Software License: + +By obtaining, using, and/or copying this software and/or its associated documentation, you agree that you have read, understood, and will comply with the following terms and conditions: + +Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Secret Labs AB or the author not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. + +SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/third-party-licences/psycopg2.txt b/third-party-licences/psycopg2.txt new file mode 100644 index 000000000..360a44f6b --- /dev/null +++ b/third-party-licences/psycopg2.txt @@ -0,0 +1,54 @@ +psycopg2 and the LGPL +===================== + +psycopg2 is free software: you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +psycopg2 is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +License for more details. + +In addition, as a special exception, the copyright holders give +permission to link this program with the OpenSSL library (or with +modified versions of OpenSSL that use the same license as OpenSSL), +and distribute linked combinations including the two. + +You must obey the GNU Lesser General Public License in all respects for +all of the code used other than OpenSSL. If you modify file(s) with this +exception, you may extend this exception to your version of the file(s), +but you are not obligated to do so. If you do not wish to do so, delete +this exception statement from your version. If you delete this exception +statement from all source files in the program, then also delete it here. + +You should have received a copy of the GNU Lesser General Public License +along with psycopg2 (see the doc/ directory.) +If not, see . + + +Alternative licenses +==================== + +If you prefer you can use the Zope Database Adapter ZPsycopgDA (i.e., +every file inside the ZPsycopgDA directory) user the ZPL license as +published on the Zope web site, http://www.zope.org/Resources/ZPL. + +Also, the following BSD-like license applies (at your option) to the +files following the pattern psycopg/adapter*.{h,c} and +psycopg/microprotocol*.{h,c}: + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product documentation + would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. diff --git a/third-party-licences/pydocstyle.txt b/third-party-licences/pydocstyle.txt new file mode 100644 index 000000000..c13417d3c --- /dev/null +++ b/third-party-licences/pydocstyle.txt @@ -0,0 +1,21 @@ +Copyright (c) 2012 GreenSteam, + +Copyright (c) 2014-2017 Amir Rachum, + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third-party-licences/python-markdown-math.txt b/third-party-licences/python-markdown-math.txt new file mode 100644 index 000000000..35d53c582 --- /dev/null +++ b/third-party-licences/python-markdown-math.txt @@ -0,0 +1,27 @@ +Copyright 2015-2017 Dmitry Shachnev . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the author nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/third-party-licences/pyyaml.txt b/third-party-licences/pyyaml.txt new file mode 100644 index 000000000..050ced23f --- /dev/null +++ b/third-party-licences/pyyaml.txt @@ -0,0 +1,19 @@ +Copyright (c) 2006 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third-party-licences/sphinx-rtd-theme.txt b/third-party-licences/sphinx-rtd-theme.txt new file mode 100644 index 000000000..921f07388 --- /dev/null +++ b/third-party-licences/sphinx-rtd-theme.txt @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Dave Snider + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/third-party-licences/sphinx.txt b/third-party-licences/sphinx.txt new file mode 100644 index 000000000..aa87ad898 --- /dev/null +++ b/third-party-licences/sphinx.txt @@ -0,0 +1,280 @@ +License for Sphinx +================== + +Copyright (c) 2007-2017 by the Sphinx team (see AUTHORS file). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Licenses for incorporated software +================================== + +The pgen2 package, included in this distribution under the name +sphinx.pycode.pgen2, is available in the Python 2.6 distribution under +the PSF license agreement for Python: + +---------------------------------------------------------------------- +Copyright © 2001-2008 Python Software Foundation; All Rights Reserved. + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation + ("PSF"), and the Individual or Organization ("Licensee") accessing + and otherwise using Python 2.6 software in source or binary form + and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF + hereby grants Licensee a nonexclusive, royalty-free, world-wide + license to reproduce, analyze, test, perform and/or display + publicly, prepare derivative works, distribute, and otherwise use + Python 2.6 alone or in any derivative version, provided, however, + that PSF's License Agreement and PSF's notice of copyright, i.e., + "Copyright © 2001-2008 Python Software Foundation; All Rights + Reserved" are retained in Python 2.6 alone or in any derivative + version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on + or incorporates Python 2.6 or any part thereof, and wants to make + the derivative work available to others as provided herein, then + Licensee hereby agrees to include in any such work a brief summary + of the changes made to Python 2.6. + +4. PSF is making Python 2.6 available to Licensee on an "AS IS" basis. + PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY + WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY + REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY + PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 2.6 WILL NOT INFRINGE + ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON + 2.6 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS + AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON + 2.6, OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY + THEREOF. + +6. This License Agreement will automatically terminate upon a material + breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any + relationship of agency, partnership, or joint venture between PSF + and Licensee. This License Agreement does not grant permission to + use PSF trademarks or trade name in a trademark sense to endorse or + promote products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python 2.6, Licensee + agrees to be bound by the terms and conditions of this License + Agreement. +---------------------------------------------------------------------- + +The included smartypants module, included as sphinx.util.smartypants, +is available under the following license: + +---------------------------------------------------------------------- +SmartyPants_ license:: + + Copyright (c) 2003 John Gruber + (http://daringfireball.net/) + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + * Neither the name "SmartyPants" nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + + This software is provided by the copyright holders and + contributors "as is" and any express or implied warranties, + including, but not limited to, the implied warranties of + merchantability and fitness for a particular purpose are + disclaimed. In no event shall the copyright owner or contributors + be liable for any direct, indirect, incidental, special, + exemplary, or consequential damages (including, but not limited + to, procurement of substitute goods or services; loss of use, + data, or profits; or business interruption) however caused and on + any theory of liability, whether in contract, strict liability, or + tort (including negligence or otherwise) arising in any way out of + the use of this software, even if advised of the possibility of + such damage. + + +smartypants.py license:: + + smartypants.py is a derivative work of SmartyPants. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + This software is provided by the copyright holders and + contributors "as is" and any express or implied warranties, + including, but not limited to, the implied warranties of + merchantability and fitness for a particular purpose are + disclaimed. In no event shall the copyright owner or contributors + be liable for any direct, indirect, incidental, special, + exemplary, or consequential damages (including, but not limited + to, procurement of substitute goods or services; loss of use, + data, or profits; or business interruption) however caused and on + any theory of liability, whether in contract, strict liability, or + tort (including negligence or otherwise) arising in any way out of + the use of this software, even if advised of the possibility of + such damage. +---------------------------------------------------------------------- + +The ElementTree package, included in this distribution in +test/etree13, is available under the following license: + +---------------------------------------------------------------------- +The ElementTree toolkit is + +Copyright (c) 1999-2007 by Fredrik Lundh + +By obtaining, using, and/or copying this software and/or its +associated documentation, you agree that you have read, understood, +and will comply with the following terms and conditions: + +Permission to use, copy, modify, and distribute this software and its +associated documentation for any purpose and without fee is hereby +granted, provided that the above copyright notice appears in all +copies, and that both that copyright notice and this permission notice +appear in supporting documentation, and that the name of Secret Labs +AB or the author not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- ABILITY +AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +---------------------------------------------------------------------- + +The included JQuery JavaScript library is available under the MIT +license: + +---------------------------------------------------------------------- +Copyright (c) 2008 John Resig, http://jquery.com/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +---------------------------------------------------------------------- + +The included Underscore JavaScript library is available under the MIT +license: + +---------------------------------------------------------------------- +Copyright (c) 2009 Jeremy Ashkenas, DocumentCloud + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +------------------------------------------------------------------------------- + +The included implementation of NumpyDocstring._parse_numpydoc_see_also_section +was derived from code under the following license: + +------------------------------------------------------------------------------- + +Copyright (C) 2008 Stefan van der Walt , Pauli Virtanen + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +------------------------------------------------------------------------------- diff --git a/third-party-licences/sticky-state.txt b/third-party-licences/sticky-state.txt new file mode 100644 index 000000000..1f95d26c5 --- /dev/null +++ b/third-party-licences/sticky-state.txt @@ -0,0 +1,5 @@ +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/third-party-licences/verto.txt b/third-party-licences/verto.txt new file mode 100644 index 000000000..c4dc62f6f --- /dev/null +++ b/third-party-licences/verto.txt @@ -0,0 +1,32 @@ +Copyright (c) University of Canterbury Computer Science Education Research +and contributors. + +The following license applies to all parts of this software except as +documented below: + +==== + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +==== + +All packages used by this software may have their own licenses; we recommend +you read them, as their terms may differ from the terms above. diff --git a/third-party-licences/wheel.txt b/third-party-licences/wheel.txt new file mode 100644 index 000000000..c3441e6cc --- /dev/null +++ b/third-party-licences/wheel.txt @@ -0,0 +1,22 @@ +"wheel" copyright (c) 2012-2014 Daniel Holth and +contributors. + +The MIT License + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE.