├── .circleci └── config.yml ├── .coveragerc ├── .dockerignore ├── .env ├── .github ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── Bug_report.md │ ├── Feature_request.md │ └── Support_question.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .tx └── config ├── AUTHORS ├── CHANGELOG.md ├── Dockerfile ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── README.rst ├── bin ├── clone_demo_course └── pytest ├── codecov.yml ├── docker-compose.yml ├── docker └── files │ └── etc │ └── nginx │ └── conf.d │ └── lms.conf ├── docs ├── Makefile ├── _static │ └── theme_overrides.css ├── conf.py ├── developer_guide.rst ├── getting_started.rst ├── index.rst ├── make.bat ├── readme.rst └── testing.rst ├── dredd.yml ├── edx-platform ├── config │ ├── cms │ │ └── docker_run_development.py │ └── lms │ │ ├── docker_run_development.py │ │ └── docker_run_test.py └── reports │ └── .gitkeep ├── fonzie-v1-0.apib ├── fonzie ├── __init__.py ├── apps.py ├── conf │ └── locale │ │ └── config.yaml ├── models.py ├── urls │ ├── __init__.py │ ├── acl.py │ ├── lms_root.py │ ├── status.py │ └── user.py └── views │ ├── __init__.py │ ├── acl.py │ ├── status.py │ └── user.py ├── locale └── config.yaml ├── manage.py ├── openedx.yaml ├── pylintrc ├── pylintrc_tweaks ├── requirements-dev.txt ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── __init__.py └── views ├── __init__.py ├── test_acl.py ├── test_status.py ├── test_user.py └── test_versioning.py /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | # ---- Project jobs ---- 4 | # Check that the git history is clean and complies with our expectations 5 | lint-git: 6 | docker: 7 | - image: cimg/python:3.7 8 | auth: 9 | username: $DOCKER_USER 10 | password: $DOCKER_PASS 11 | working_directory: ~/fun 12 | steps: 13 | - checkout 14 | # Make sure the changes don't add a "print" statement to the code base. 15 | # We should exclude the ".circleci" folder from the search as the very command that checks 16 | # the absence of "print" is including a "print(" itself. 17 | - run: 18 | name: enforce absence of print statements in code 19 | command: | 20 | ! git diff origin/master..HEAD -- . ':(exclude).circleci' | grep "print(" 21 | - run: 22 | name: Check absence of fixup commits 23 | command: | 24 | ! git log | grep 'fixup!' 25 | - run: 26 | name: Install gitlint 27 | command: | 28 | pip install --user gitlint 29 | - run: 30 | name: lint commit messages added to master 31 | command: | 32 | ~/.local/bin/gitlint --commits origin/master..HEAD 33 | 34 | # Check that the CHANGELOG has been updated in the current branch 35 | check-changelog: 36 | docker: 37 | - image: cimg/base:2023.12 38 | auth: 39 | username: $DOCKER_USER 40 | password: $DOCKER_PASS 41 | working_directory: ~/fun 42 | steps: 43 | - checkout 44 | - run: 45 | name: Check that the CHANGELOG has been modified in the current branch 46 | command: | 47 | git whatchanged --name-only --pretty="" origin..HEAD | grep CHANGELOG 48 | 49 | # Check that the CHANGELOG max line length does not exceed 80 characters 50 | lint-changelog: 51 | docker: 52 | - image: debian:stretch 53 | auth: 54 | username: $DOCKER_USER 55 | password: $DOCKER_PASS 56 | working_directory: ~/fun 57 | steps: 58 | - checkout 59 | - run: 60 | name: Check CHANGELOG max line length 61 | command: | 62 | # Get the longuest line width (ignoring release links) 63 | test $(cat CHANGELOG.md | grep -Ev "^\[.*\]: https://github.com/openfun" | wc -L) -le 80 64 | 65 | # ---- Backend jobs ---- 66 | # Build backend development environment 67 | build-back: 68 | docker: 69 | - image: cimg/python:2.7 70 | auth: 71 | username: $DOCKER_USER 72 | password: $DOCKER_PASS 73 | working_directory: ~/fun 74 | steps: 75 | - checkout 76 | - restore_cache: 77 | keys: 78 | - v1-back-dependencies-{{ .Revision }} 79 | - v1-back-dependencies 80 | - run: 81 | name: Install development dependencies 82 | command: pip install --user .[dev,doc,quality,test,ci] 83 | - save_cache: 84 | paths: 85 | - ~/.local 86 | key: v1-back-dependencies-{{ .Revision }} 87 | 88 | build-docs: 89 | docker: 90 | - image: cimg/python:2.7 91 | auth: 92 | username: $DOCKER_USER 93 | password: $DOCKER_PASS 94 | working_directory: ~/fun 95 | steps: 96 | - checkout 97 | - restore_cache: 98 | keys: 99 | - v1-back-dependencies-{{ .Revision }} 100 | - run: 101 | name: Add ~/.local/bin to the PATH 102 | command: | 103 | echo 'export PATH=$PATH:$HOME/.local/bin' >> $BASH_ENV 104 | - run: 105 | name: Compile documentation 106 | command: | 107 | make -C docs html 108 | 109 | lint-back: 110 | docker: 111 | - image: cimg/python:2.7 112 | auth: 113 | username: $DOCKER_USER 114 | password: $DOCKER_PASS 115 | working_directory: ~/fun 116 | steps: 117 | - checkout 118 | - restore_cache: 119 | keys: 120 | - v1-back-dependencies-{{ .Revision }} 121 | - run: 122 | name: Add ~/.local/bin to the PATH 123 | command: echo 'export PATH=${PATH}:${HOME}/.local/bin' >> $BASH_ENV 124 | - run: 125 | name: Lint code with pylint 126 | command: pylint fonzie tests 127 | - run: 128 | name: Lint code with pylint (py3k) 129 | command: pylint --py3k fonzie tests 130 | - run: 131 | name: Lint code with bandit 132 | command: bandit -qr fonzie 133 | - run: 134 | name: Lint code with pycodestyle 135 | command: pycodestyle fonzie tests 136 | - run: 137 | name: Lint doc with pydocstyle 138 | command: pydocstyle fonzie tests 139 | - run: 140 | name: Lint code with isort 141 | command: | 142 | isort --check-only --recursive tests fonzie manage.py setup.py 143 | - run: 144 | name: Lint code with pyroma 145 | command: | 146 | pyroma . 147 | - run: 148 | name: Lint documentation with doc8 149 | command: | 150 | doc8 --ignore-path docs/_build README.rst docs 151 | 152 | test-back: 153 | machine: true 154 | working_directory: ~/fun 155 | steps: 156 | - checkout 157 | - restore_cache: 158 | keys: 159 | - v1-back-dependencies-{{ .Revision }} 160 | - run: 161 | name: Build Docker image 162 | command: make build 163 | - run: 164 | name: Run tests 165 | command: make test 166 | # To publish coverage report, you will need to define the CODECOV_TOKEN 167 | # secret environment variables in CircleCI UI 168 | - run: 169 | name: Publish coverage report 170 | command: make report 171 | 172 | test-spec: 173 | machine: true 174 | working_directory: ~/fun 175 | steps: 176 | - checkout 177 | - restore_cache: 178 | keys: 179 | - v1-edxapp-sql-dump 180 | - run: 181 | name: Build Docker image 182 | command: make build 183 | - run: 184 | name: Start the test stack 185 | command: make run 186 | - run: 187 | name: Wait for database to be up 188 | command: docker-compose exec lms dockerize -wait tcp://mysql:3306 -timeout 60s 189 | - run: 190 | name: Load edxapp SQL dump (if any) 191 | command: | 192 | if [[ -f ~/sql/dump.sql ]]; then 193 | # FIXME: docker-compose is having issues with huge files streamed 194 | # via a pty/stdin, so we bypass compose to use raw docker exec 195 | # command. 196 | docker exec -i $(docker-compose ps -q mysql) mysql -u root edxapp < ~/sql/dump.sql 197 | else 198 | echo "There is no SQL dump available for now (you'll have to wait)." 199 | fi 200 | - run: 201 | name: Run database migrations 202 | command: make migrate 203 | - run: 204 | name: Dump SQL state to speed up next run migrations 205 | command: | 206 | if [[ ! -f ~/sql/dump.sql ]]; then 207 | mkdir -p ~/sql 208 | # FIXME: docker-compose is having issues with huge files streamed 209 | # via a pty/stdin, so we bypass compose to use raw docker exec 210 | # command. 211 | docker exec -i $(docker-compose ps -q mysql) mysqldump -u root edxapp > ~/sql/dump.sql 212 | else 213 | echo "SQL dump file has already been generated. Skipping this step." 214 | fi 215 | - save_cache: 216 | paths: 217 | - ~/sql/dump.sql 218 | key: v1-edxapp-sql-dump 219 | - run: 220 | name: Run spec tests 221 | command: make test-spec 222 | 223 | # ---- Packaging jobs ---- 224 | package-back: 225 | docker: 226 | - image: cimg/python:2.7 227 | auth: 228 | username: $DOCKER_USER 229 | password: $DOCKER_PASS 230 | working_directory: ~/fun 231 | steps: 232 | - checkout 233 | - restore_cache: 234 | keys: 235 | - v1-back-dependencies-{{ .Revision }} 236 | - run: 237 | name: Build python package 238 | command: python setup.py sdist bdist_wheel 239 | - run: 240 | name: Check the package README 241 | command: ~/.local/bin/twine check dist/* 242 | # Persist build packages to the workspace 243 | - persist_to_workspace: 244 | root: ~/fun 245 | paths: 246 | - dist 247 | # Store packages as artifacts to download/test them 248 | - store_artifacts: 249 | path: ~/fun/dist 250 | 251 | # Publishing to PyPI requires that: 252 | # * you already registered to pypi.org 253 | # * you have define both the TWINE_USERNAME & TWINE_PASSWORD secret 254 | # environment variables in CircleCI UI (with your PyPI credentials) 255 | pypi: 256 | docker: 257 | - image: cimg/python:2.7 258 | auth: 259 | username: $DOCKER_USER 260 | password: $DOCKER_PASS 261 | working_directory: ~/fun 262 | steps: 263 | - checkout 264 | - restore_cache: 265 | keys: 266 | - v1-back-dependencies-{{ .Revision }} 267 | # Restore built python packages 268 | - attach_workspace: 269 | at: ~/fun 270 | - run: 271 | name: List built packages 272 | command: ls dist/* 273 | - run: 274 | name: Upload built packages to PyPI 275 | command: ~/.local/bin/twine upload dist/* 276 | 277 | workflows: 278 | version: 2 279 | 280 | fonzie: 281 | jobs: 282 | # Git jobs 283 | # 284 | # Check validity of git history 285 | - lint-git: 286 | filters: 287 | tags: 288 | only: /.*/ 289 | # Check CHANGELOG update 290 | - check-changelog: 291 | filters: 292 | branches: 293 | ignore: master 294 | tags: 295 | only: /(?!^v).*/ 296 | - lint-changelog: 297 | filters: 298 | branches: 299 | ignore: master 300 | tags: 301 | only: /.*/ 302 | 303 | # Backend jobs 304 | # 305 | # Build, lint and test application code 306 | - build-back: 307 | filters: 308 | tags: 309 | only: /.*/ 310 | - build-docs: 311 | requires: 312 | - build-back 313 | filters: 314 | tags: 315 | only: /.*/ 316 | - lint-back: 317 | requires: 318 | - build-back 319 | filters: 320 | tags: 321 | only: /.*/ 322 | - test-back: 323 | requires: 324 | - lint-back 325 | filters: 326 | tags: 327 | only: /.*/ 328 | - test-spec: 329 | requires: 330 | - lint-back 331 | filters: 332 | tags: 333 | only: /.*/ 334 | 335 | # Packaging: python 336 | # 337 | # Build the python package 338 | - package-back: 339 | requires: 340 | - build-docs 341 | - test-spec 342 | - test-back 343 | filters: 344 | tags: 345 | only: /.*/ 346 | 347 | # PyPI publication. 348 | # 349 | # Publish python package to PYPI only if all build, lint and test jobs 350 | # succeed and it has been tagged with a tag starting with the letter v 351 | - pypi: 352 | requires: 353 | - package-back 354 | filters: 355 | branches: 356 | ignore: /.*/ 357 | tags: 358 | only: /^v.*/ 359 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | data_file = .coverage 4 | source=fonzie 5 | omit = 6 | test_settings 7 | *migrations* 8 | *admin.py 9 | *static* 10 | *templates* 11 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .coverage 2 | .coveragerc 3 | .git 4 | .tox 5 | .tx 6 | node_modules 7 | venv 8 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # Django 2 | SERVICE_VARIANT=lms 3 | DJANGO_SETTINGS_MODULE=lms.envs.fun.docker_run_development 4 | 5 | # Database 6 | MYSQL_ROOT_PASSWORD= 7 | MYSQL_ALLOW_EMPTY_PASSWORD=yes 8 | MYSQL_DATABASE=edxapp 9 | MYSQL_USER=edxapp_user 10 | MYSQL_PASSWORD=password 11 | 12 | # Email 13 | EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend 14 | EMAIL_HOST=mailcatcher 15 | EMAIL_PORT=1025 16 | 17 | # Python 18 | PYTHONUNBUFFERED=1 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug Report 3 | about: If something is not working as expected 🤔. 4 | 5 | --- 6 | 7 | ## Bug Report 8 | 9 | **Problematic behavior** 10 | A clear and concise description of the behavior. 11 | 12 | **Expected behavior/code** 13 | A clear and concise description of what you expected to happen (or code). 14 | 15 | **Steps to Reproduce** 16 | 1. Do this... 17 | 2. Then this... 18 | 3. And then the bug happens! 19 | 20 | **Environment** 21 | - Joanie version: 22 | - Platform: 23 | 24 | **Possible Solution** 25 | 26 | 27 | **Additional context/Screenshots** 28 | Add any other context about the problem here. If applicable, add screenshots to help explain. 29 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ✨ Feature Request 3 | about: I have a suggestion (and may want to build it 💪)! 4 | 5 | --- 6 | 7 | ## Feature Request 8 | 9 | **Is your feature request related to a problem or unsupported use case? Please describe.** 10 | A clear and concise description of what the problem is. For example: I need to do some task and I have an issue... 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. Add any considered drawbacks. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Discovery, Documentation, Adoption, Migration Strategy** 19 | If you can, explain how users will be able to use this and possibly write out a version the docs (if applicable). 20 | Maybe a screenshot or design? 21 | 22 | **Do you want to work on it through a Pull Request?** 23 | 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Support_question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🤗 Support Question 3 | about: If you have a question 💬, or something was not clear from the docs! 4 | 5 | --- 6 | 7 | 9 | 10 | --- 11 | 12 | Please make sure you have read our [main Readme](https://github.com/openfun/joanie). 13 | 14 | Also make sure it was not already answered in [an open or close issue](https://github.com/openfun/joanie/issues). 15 | 16 | If your question was not covered, and you feel like it should be, fire away! We'd love to improve our docs! 👌 17 | 18 | **Topic** 19 | What's the general area of your question: for example, docker setup, search functionality, open-edx interoperability... 20 | 21 | **Question** 22 | Try to be as specific as possible so we can help you as best we can. Please be patient 🙏 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Purpose 2 | 3 | Description... 4 | 5 | 6 | ## Proposal 7 | 8 | Description... 9 | 10 | - [] item 1... 11 | - [] item 2... 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | edx-demo-course/ 3 | 4 | *.py[cod] 5 | __pycache__ 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Packages 11 | *.egg 12 | *.egg-info 13 | dist 14 | build/ 15 | eggs 16 | parts 17 | var 18 | sdist 19 | develop-eggs 20 | .installed.cfg 21 | lib 22 | lib64 23 | 24 | # Virtual environments 25 | .venv 26 | venv 27 | 28 | # Installer logs 29 | pip-log.txt 30 | 31 | # Unit test / coverage reports 32 | .cache/ 33 | .coverage 34 | .coverage.* 35 | .pytest_cache/ 36 | .tox 37 | coverage.xml 38 | htmlcov/ 39 | edx-platform/reports/cover/ 40 | 41 | # Translations 42 | *.mo 43 | 44 | # IDEs and text editors 45 | *~ 46 | *.swp 47 | .idea/ 48 | .project 49 | .pycharm_helpers/ 50 | .pydevproject 51 | .vscode 52 | 53 | # The Silver Searcher 54 | .agignore 55 | 56 | # OS X artifacts 57 | *.DS_Store 58 | 59 | # Logging 60 | log/ 61 | logs/ 62 | chromedriver.log 63 | ghostdriver.log 64 | 65 | # Complexity 66 | output/*.html 67 | output/*/index.html 68 | 69 | # Sphinx 70 | docs/_build 71 | docs/modules.rst 72 | docs/fonzie.rst 73 | docs/fonzie.*.rst 74 | 75 | # Private requirements 76 | requirements/private.in 77 | requirements/private.txt 78 | 79 | # Development task artifacts 80 | default.db 81 | 82 | # Node.js 83 | node_modules 84 | yarn-error.log 85 | 86 | -------------------------------------------------------------------------------- /.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | 4 | [edx-platform.fonzie] 5 | file_filter = fonzie/conf/locale//LC_MESSAGES/django.po 6 | source_file = fonzie/conf/locale/en/LC_MESSAGES/django.po 7 | source_lang = en 8 | type = PO 9 | 10 | [edx-platform.fonzie-js] 11 | file_filter = fonzie/conf/locale//LC_MESSAGES/djangojs.po 12 | source_file = fonzie/conf/locale/en/LC_MESSAGES/djangojs.po 13 | source_lang = en 14 | type = PO 15 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Open FUN (France Universite Numerique) 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic 7 | Versioning](https://semver.org/spec/v2.0.0.html). 8 | 9 | 10 | ## [Unreleased] 11 | 12 | ## [0.7.0] - 2024-04-04 13 | 14 | ### Added 15 | 16 | - Send user newsletter subscription status in the user token 17 | 18 | ## [0.6.0] - 2024-03-13 19 | 20 | ### Added 21 | 22 | - Set cross domain csrf token on user api get request 23 | 24 | ## [0.5.0] - 2023-12-07 25 | 26 | ### Added 27 | 28 | - Bind user permissions into the claim of the JWT Token 29 | (is_active, is_staff, is_superuser) 30 | 31 | ### Fixed 32 | 33 | - Return user information in the User API endpoint even 34 | if the user is not active 35 | 36 | ## [0.4.0] - 2022-07-28 37 | 38 | ### Added 39 | 40 | - Bind user preference language into the claim of the JWT Token 41 | 42 | ## [0.3.0] - 2022-04-07 43 | 44 | ### Added 45 | 46 | - Add a User API endpoint to first generate a JWT for Authentication purpose 47 | from Third Party Application 48 | 49 | ### Changed 50 | 51 | - Generate JWT Token through Simple JWT's AccessToken class 52 | 53 | ## [0.2.1] - 2019-10-10 54 | 55 | ### Fixed 56 | 57 | - ACL: Support filenames using `@` and `+` (e.g. course problem responses 58 | report) 59 | - Fix CI tree creation order 60 | 61 | ## [0.2.0] - 2019-05-21 62 | 63 | ### Added 64 | 65 | - Add ACL view to control access to instructor dashboard exported files 66 | - Add `cms` and `nginx` services to development environment 67 | - Add a POC using a schema-driven development (using 68 | `API Blueprint `\_). 69 | 70 | 71 | [unreleased]: https://github.com/openfun/fonzie/compare/v0.7.0...master 72 | [0.7.0]: https://github.com/openfun/fonzie/compare/v0.6.0...v0.7.0 73 | [0.6.0]: https://github.com/openfun/fonzie/compare/v0.5.0...v0.6.0 74 | [0.5.0]: https://github.com/openfun/fonzie/compare/v0.4.0...v0.5.0 75 | [0.4.0]: https://github.com/openfun/fonzie/compare/v0.3.0...v0.4.0 76 | [0.3.0]: https://github.com/openfun/fonzie/compare/v0.2.1...v0.3.0 77 | [0.2.1]: https://github.com/openfun/fonzie/compare/v0.2.0...v0.2.1 78 | [0.2.0]: https://github.com/openfun/fonzie/compare/b31adef...v0.2.0 79 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM fundocker/edxapp:hawthorn.1-3.3.1 2 | 3 | ARG USER_ID=1000 4 | ARG GROUP_ID=1000 5 | 6 | # Switch back to a priviledged user to perform base installation 7 | USER root:root 8 | 9 | # Install dockerize to wait for mysql before running the container command 10 | # (and prevent connection issues) 11 | ENV DOCKERIZE_VERSION v0.6.1 12 | RUN python -c "import requests;open('dockerize-linux-amd64.tar.gz', 'wb').write(requests.get('https://github.com/jwilder/dockerize/releases/download/${DOCKERIZE_VERSION}/dockerize-linux-amd64-${DOCKERIZE_VERSION}.tar.gz', allow_redirects=True).content)" && \ 13 | tar -C /usr/local/bin -xzvf dockerize-linux-amd64.tar.gz && \ 14 | rm dockerize-linux-amd64.tar.gz 15 | 16 | # Add the non-privileged user that will run the application 17 | RUN groupadd -f --gid ${GROUP_ID} edx && \ 18 | useradd --uid ${USER_ID} --gid ${GROUP_ID} --home /edx edx 19 | 20 | # Allow the edx user to create files in /edx/var (required to perform database 21 | # migrations) 22 | RUN mkdir /edx/var && \ 23 | chown edx:edx /edx/var 24 | 25 | # The /edx/var tree should be writable by the running user to perform 26 | # collectstatic and migrations. 27 | RUN chown -R edx:edx /edx/var 28 | 29 | # Copy the app to the working directory 30 | COPY --chown=edx:edx . /edx/app/fonzie/ 31 | 32 | # Install development dependencies and perform a base installation of the app 33 | RUN cd /edx/app/fonzie/ && \ 34 | pip install --no-cache-dir -r requirements-dev.txt 35 | 36 | # Switch to an un-privileged user matching the host user to prevent permission 37 | # issues with volumes (host folders) 38 | USER ${USER_ID}:${GROUP_ID} 39 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | GNU AFFERO GENERAL PUBLIC LICENSE 3 | Version 3, 19 November 2007 4 | 5 | Copyright (C) 2007 Free Software Foundation, Inc. 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The GNU Affero General Public License is a free, copyleft license for 12 | software and other kinds of works, specifically designed to ensure 13 | cooperation with the community in the case of network server software. 14 | 15 | The licenses for most software and other practical works are designed 16 | to take away your freedom to share and change the works. By contrast, 17 | our General Public Licenses are intended to guarantee your freedom to 18 | share and change all versions of a program--to make sure it remains free 19 | software for all its users. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | them if you wish), that you receive source code or can get it if you 25 | want it, that you can change the software or use pieces of it in new 26 | free programs, and that you know you can do these things. 27 | 28 | Developers that use our General Public Licenses protect your rights 29 | with two steps: (1) assert copyright on the software, and (2) offer 30 | you this License which gives you legal permission to copy, distribute 31 | and/or modify the software. 32 | 33 | A secondary benefit of defending all users' freedom is that 34 | improvements made in alternate versions of the program, if they 35 | receive widespread use, become available for other developers to 36 | incorporate. Many developers of free software are heartened and 37 | encouraged by the resulting cooperation. However, in the case of 38 | software used on network servers, this result may fail to come about. 39 | The GNU General Public License permits making a modified version and 40 | letting the public access it on a server without ever releasing its 41 | source code to the public. 42 | 43 | The GNU Affero General Public License is designed specifically to 44 | ensure that, in such cases, the modified source code becomes available 45 | to the community. It requires the operator of a network server to 46 | provide the source code of the modified version running there to the 47 | users of that server. Therefore, public use of a modified version, on 48 | a publicly accessible server, gives the public access to the source 49 | code of the modified version. 50 | 51 | An older license, called the Affero General Public License and 52 | published by Affero, was designed to accomplish similar goals. This is 53 | a different license, not a version of the Affero GPL, but Affero has 54 | released a new version of the Affero GPL which permits relicensing under 55 | this license. 56 | 57 | The precise terms and conditions for copying, distribution and 58 | modification follow. 59 | 60 | TERMS AND CONDITIONS 61 | 62 | 0. Definitions. 63 | 64 | "This License" refers to version 3 of the GNU Affero General Public License. 65 | 66 | "Copyright" also means copyright-like laws that apply to other kinds of 67 | works, such as semiconductor masks. 68 | 69 | "The Program" refers to any copyrightable work licensed under this 70 | License. Each licensee is addressed as "you". "Licensees" and 71 | "recipients" may be individuals or organizations. 72 | 73 | To "modify" a work means to copy from or adapt all or part of the work 74 | in a fashion requiring copyright permission, other than the making of an 75 | exact copy. The resulting work is called a "modified version" of the 76 | earlier work or a work "based on" the earlier work. 77 | 78 | A "covered work" means either the unmodified Program or a work based 79 | on the Program. 80 | 81 | To "propagate" a work means to do anything with it that, without 82 | permission, would make you directly or secondarily liable for 83 | infringement under applicable copyright law, except executing it on a 84 | computer or modifying a private copy. Propagation includes copying, 85 | distribution (with or without modification), making available to the 86 | public, and in some countries other activities as well. 87 | 88 | To "convey" a work means any kind of propagation that enables other 89 | parties to make or receive copies. Mere interaction with a user through 90 | a computer network, with no transfer of a copy, is not conveying. 91 | 92 | An interactive user interface displays "Appropriate Legal Notices" 93 | to the extent that it includes a convenient and prominently visible 94 | feature that (1) displays an appropriate copyright notice, and (2) 95 | tells the user that there is no warranty for the work (except to the 96 | extent that warranties are provided), that licensees may convey the 97 | work under this License, and how to view a copy of this License. If 98 | the interface presents a list of user commands or options, such as a 99 | menu, a prominent item in the list meets this criterion. 100 | 101 | 1. Source Code. 102 | 103 | The "source code" for a work means the preferred form of the work 104 | for making modifications to it. "Object code" means any non-source 105 | form of a work. 106 | 107 | A "Standard Interface" means an interface that either is an official 108 | standard defined by a recognized standards body, or, in the case of 109 | interfaces specified for a particular programming language, one that 110 | is widely used among developers working in that language. 111 | 112 | The "System Libraries" of an executable work include anything, other 113 | than the work as a whole, that (a) is included in the normal form of 114 | packaging a Major Component, but which is not part of that Major 115 | Component, and (b) serves only to enable use of the work with that 116 | Major Component, or to implement a Standard Interface for which an 117 | implementation is available to the public in source code form. A 118 | "Major Component", in this context, means a major essential component 119 | (kernel, window system, and so on) of the specific operating system 120 | (if any) on which the executable work runs, or a compiler used to 121 | produce the work, or an object code interpreter used to run it. 122 | 123 | The "Corresponding Source" for a work in object code form means all 124 | the source code needed to generate, install, and (for an executable 125 | work) run the object code and to modify the work, including scripts to 126 | control those activities. However, it does not include the work's 127 | System Libraries, or general-purpose tools or generally available free 128 | programs which are used unmodified in performing those activities but 129 | which are not part of the work. For example, Corresponding Source 130 | includes interface definition files associated with source files for 131 | the work, and the source code for shared libraries and dynamically 132 | linked subprograms that the work is specifically designed to require, 133 | such as by intimate data communication or control flow between those 134 | subprograms and other parts of the work. 135 | 136 | The Corresponding Source need not include anything that users 137 | can regenerate automatically from other parts of the Corresponding 138 | Source. 139 | 140 | The Corresponding Source for a work in source code form is that 141 | same work. 142 | 143 | 2. Basic Permissions. 144 | 145 | All rights granted under this License are granted for the term of 146 | copyright on the Program, and are irrevocable provided the stated 147 | conditions are met. This License explicitly affirms your unlimited 148 | permission to run the unmodified Program. The output from running a 149 | covered work is covered by this License only if the output, given its 150 | content, constitutes a covered work. This License acknowledges your 151 | rights of fair use or other equivalent, as provided by copyright law. 152 | 153 | You may make, run and propagate covered works that you do not 154 | convey, without conditions so long as your license otherwise remains 155 | in force. You may convey covered works to others for the sole purpose 156 | of having them make modifications exclusively for you, or provide you 157 | with facilities for running those works, provided that you comply with 158 | the terms of this License in conveying all material for which you do 159 | not control copyright. Those thus making or running the covered works 160 | for you must do so exclusively on your behalf, under your direction 161 | and control, on terms that prohibit them from making any copies of 162 | your copyrighted material outside their relationship with you. 163 | 164 | Conveying under any other circumstances is permitted solely under 165 | the conditions stated below. Sublicensing is not allowed; section 10 166 | makes it unnecessary. 167 | 168 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 169 | 170 | No covered work shall be deemed part of an effective technological 171 | measure under any applicable law fulfilling obligations under article 172 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 173 | similar laws prohibiting or restricting circumvention of such 174 | measures. 175 | 176 | When you convey a covered work, you waive any legal power to forbid 177 | circumvention of technological measures to the extent such circumvention 178 | is effected by exercising rights under this License with respect to 179 | the covered work, and you disclaim any intention to limit operation or 180 | modification of the work as a means of enforcing, against the work's 181 | users, your or third parties' legal rights to forbid circumvention of 182 | technological measures. 183 | 184 | 4. Conveying Verbatim Copies. 185 | 186 | You may convey verbatim copies of the Program's source code as you 187 | receive it, in any medium, provided that you conspicuously and 188 | appropriately publish on each copy an appropriate copyright notice; 189 | keep intact all notices stating that this License and any 190 | non-permissive terms added in accord with section 7 apply to the code; 191 | keep intact all notices of the absence of any warranty; and give all 192 | recipients a copy of this License along with the Program. 193 | 194 | You may charge any price or no price for each copy that you convey, 195 | and you may offer support or warranty protection for a fee. 196 | 197 | 5. Conveying Modified Source Versions. 198 | 199 | You may convey a work based on the Program, or the modifications to 200 | produce it from the Program, in the form of source code under the 201 | terms of section 4, provided that you also meet all of these conditions: 202 | 203 | a) The work must carry prominent notices stating that you modified 204 | it, and giving a relevant date. 205 | 206 | b) The work must carry prominent notices stating that it is 207 | released under this License and any conditions added under section 208 | 7. This requirement modifies the requirement in section 4 to 209 | "keep intact all notices". 210 | 211 | c) You must license the entire work, as a whole, under this 212 | License to anyone who comes into possession of a copy. This 213 | License will therefore apply, along with any applicable section 7 214 | additional terms, to the whole of the work, and all its parts, 215 | regardless of how they are packaged. This License gives no 216 | permission to license the work in any other way, but it does not 217 | invalidate such permission if you have separately received it. 218 | 219 | d) If the work has interactive user interfaces, each must display 220 | Appropriate Legal Notices; however, if the Program has interactive 221 | interfaces that do not display Appropriate Legal Notices, your 222 | work need not make them do so. 223 | 224 | A compilation of a covered work with other separate and independent 225 | works, which are not by their nature extensions of the covered work, 226 | and which are not combined with it such as to form a larger program, 227 | in or on a volume of a storage or distribution medium, is called an 228 | "aggregate" if the compilation and its resulting copyright are not 229 | used to limit the access or legal rights of the compilation's users 230 | beyond what the individual works permit. Inclusion of a covered work 231 | in an aggregate does not cause this License to apply to the other 232 | parts of the aggregate. 233 | 234 | 6. Conveying Non-Source Forms. 235 | 236 | You may convey a covered work in object code form under the terms 237 | of sections 4 and 5, provided that you also convey the 238 | machine-readable Corresponding Source under the terms of this License, 239 | in one of these ways: 240 | 241 | a) Convey the object code in, or embodied in, a physical product 242 | (including a physical distribution medium), accompanied by the 243 | Corresponding Source fixed on a durable physical medium 244 | customarily used for software interchange. 245 | 246 | b) Convey the object code in, or embodied in, a physical product 247 | (including a physical distribution medium), accompanied by a 248 | written offer, valid for at least three years and valid for as 249 | long as you offer spare parts or customer support for that product 250 | model, to give anyone who possesses the object code either (1) a 251 | copy of the Corresponding Source for all the software in the 252 | product that is covered by this License, on a durable physical 253 | medium customarily used for software interchange, for a price no 254 | more than your reasonable cost of physically performing this 255 | conveying of source, or (2) access to copy the 256 | Corresponding Source from a network server at no charge. 257 | 258 | c) Convey individual copies of the object code with a copy of the 259 | written offer to provide the Corresponding Source. This 260 | alternative is allowed only occasionally and noncommercially, and 261 | only if you received the object code with such an offer, in accord 262 | with subsection 6b. 263 | 264 | d) Convey the object code by offering access from a designated 265 | place (gratis or for a charge), and offer equivalent access to the 266 | Corresponding Source in the same way through the same place at no 267 | further charge. You need not require recipients to copy the 268 | Corresponding Source along with the object code. If the place to 269 | copy the object code is a network server, the Corresponding Source 270 | may be on a different server (operated by you or a third party) 271 | that supports equivalent copying facilities, provided you maintain 272 | clear directions next to the object code saying where to find the 273 | Corresponding Source. Regardless of what server hosts the 274 | Corresponding Source, you remain obligated to ensure that it is 275 | available for as long as needed to satisfy these requirements. 276 | 277 | e) Convey the object code using peer-to-peer transmission, provided 278 | you inform other peers where the object code and Corresponding 279 | Source of the work are being offered to the general public at no 280 | charge under subsection 6d. 281 | 282 | A separable portion of the object code, whose source code is excluded 283 | from the Corresponding Source as a System Library, need not be 284 | included in conveying the object code work. 285 | 286 | A "User Product" is either (1) a "consumer product", which means any 287 | tangible personal property which is normally used for personal, family, 288 | or household purposes, or (2) anything designed or sold for incorporation 289 | into a dwelling. In determining whether a product is a consumer product, 290 | doubtful cases shall be resolved in favor of coverage. For a particular 291 | product received by a particular user, "normally used" refers to a 292 | typical or common use of that class of product, regardless of the status 293 | of the particular user or of the way in which the particular user 294 | actually uses, or expects or is expected to use, the product. A product 295 | is a consumer product regardless of whether the product has substantial 296 | commercial, industrial or non-consumer uses, unless such uses represent 297 | the only significant mode of use of the product. 298 | 299 | "Installation Information" for a User Product means any methods, 300 | procedures, authorization keys, or other information required to install 301 | and execute modified versions of a covered work in that User Product from 302 | a modified version of its Corresponding Source. The information must 303 | suffice to ensure that the continued functioning of the modified object 304 | code is in no case prevented or interfered with solely because 305 | modification has been made. 306 | 307 | If you convey an object code work under this section in, or with, or 308 | specifically for use in, a User Product, and the conveying occurs as 309 | part of a transaction in which the right of possession and use of the 310 | User Product is transferred to the recipient in perpetuity or for a 311 | fixed term (regardless of how the transaction is characterized), the 312 | Corresponding Source conveyed under this section must be accompanied 313 | by the Installation Information. But this requirement does not apply 314 | if neither you nor any third party retains the ability to install 315 | modified object code on the User Product (for example, the work has 316 | been installed in ROM). 317 | 318 | The requirement to provide Installation Information does not include a 319 | requirement to continue to provide support service, warranty, or updates 320 | for a work that has been modified or installed by the recipient, or for 321 | the User Product in which it has been modified or installed. Access to a 322 | network may be denied when the modification itself materially and 323 | adversely affects the operation of the network or violates the rules and 324 | protocols for communication across the network. 325 | 326 | Corresponding Source conveyed, and Installation Information provided, 327 | in accord with this section must be in a format that is publicly 328 | documented (and with an implementation available to the public in 329 | source code form), and must require no special password or key for 330 | unpacking, reading or copying. 331 | 332 | 7. Additional Terms. 333 | 334 | "Additional permissions" are terms that supplement the terms of this 335 | License by making exceptions from one or more of its conditions. 336 | Additional permissions that are applicable to the entire Program shall 337 | be treated as though they were included in this License, to the extent 338 | that they are valid under applicable law. If additional permissions 339 | apply only to part of the Program, that part may be used separately 340 | under those permissions, but the entire Program remains governed by 341 | this License without regard to the additional permissions. 342 | 343 | When you convey a copy of a covered work, you may at your option 344 | remove any additional permissions from that copy, or from any part of 345 | it. (Additional permissions may be written to require their own 346 | removal in certain cases when you modify the work.) You may place 347 | additional permissions on material, added by you to a covered work, 348 | for which you have or can give appropriate copyright permission. 349 | 350 | Notwithstanding any other provision of this License, for material you 351 | add to a covered work, you may (if authorized by the copyright holders of 352 | that material) supplement the terms of this License with terms: 353 | 354 | a) Disclaiming warranty or limiting liability differently from the 355 | terms of sections 15 and 16 of this License; or 356 | 357 | b) Requiring preservation of specified reasonable legal notices or 358 | author attributions in that material or in the Appropriate Legal 359 | Notices displayed by works containing it; or 360 | 361 | c) Prohibiting misrepresentation of the origin of that material, or 362 | requiring that modified versions of such material be marked in 363 | reasonable ways as different from the original version; or 364 | 365 | d) Limiting the use for publicity purposes of names of licensors or 366 | authors of the material; or 367 | 368 | e) Declining to grant rights under trademark law for use of some 369 | trade names, trademarks, or service marks; or 370 | 371 | f) Requiring indemnification of licensors and authors of that 372 | material by anyone who conveys the material (or modified versions of 373 | it) with contractual assumptions of liability to the recipient, for 374 | any liability that these contractual assumptions directly impose on 375 | those licensors and authors. 376 | 377 | All other non-permissive additional terms are considered "further 378 | restrictions" within the meaning of section 10. If the Program as you 379 | received it, or any part of it, contains a notice stating that it is 380 | governed by this License along with a term that is a further 381 | restriction, you may remove that term. If a license document contains 382 | a further restriction but permits relicensing or conveying under this 383 | License, you may add to a covered work material governed by the terms 384 | of that license document, provided that the further restriction does 385 | not survive such relicensing or conveying. 386 | 387 | If you add terms to a covered work in accord with this section, you 388 | must place, in the relevant source files, a statement of the 389 | additional terms that apply to those files, or a notice indicating 390 | where to find the applicable terms. 391 | 392 | Additional terms, permissive or non-permissive, may be stated in the 393 | form of a separately written license, or stated as exceptions; 394 | the above requirements apply either way. 395 | 396 | 8. Termination. 397 | 398 | You may not propagate or modify a covered work except as expressly 399 | provided under this License. Any attempt otherwise to propagate or 400 | modify it is void, and will automatically terminate your rights under 401 | this License (including any patent licenses granted under the third 402 | paragraph of section 11). 403 | 404 | However, if you cease all violation of this License, then your 405 | license from a particular copyright holder is reinstated (a) 406 | provisionally, unless and until the copyright holder explicitly and 407 | finally terminates your license, and (b) permanently, if the copyright 408 | holder fails to notify you of the violation by some reasonable means 409 | prior to 60 days after the cessation. 410 | 411 | Moreover, your license from a particular copyright holder is 412 | reinstated permanently if the copyright holder notifies you of the 413 | violation by some reasonable means, this is the first time you have 414 | received notice of violation of this License (for any work) from that 415 | copyright holder, and you cure the violation prior to 30 days after 416 | your receipt of the notice. 417 | 418 | Termination of your rights under this section does not terminate the 419 | licenses of parties who have received copies or rights from you under 420 | this License. If your rights have been terminated and not permanently 421 | reinstated, you do not qualify to receive new licenses for the same 422 | material under section 10. 423 | 424 | 9. Acceptance Not Required for Having Copies. 425 | 426 | You are not required to accept this License in order to receive or 427 | run a copy of the Program. Ancillary propagation of a covered work 428 | occurring solely as a consequence of using peer-to-peer transmission 429 | to receive a copy likewise does not require acceptance. However, 430 | nothing other than this License grants you permission to propagate or 431 | modify any covered work. These actions infringe copyright if you do 432 | not accept this License. Therefore, by modifying or propagating a 433 | covered work, you indicate your acceptance of this License to do so. 434 | 435 | 10. Automatic Licensing of Downstream Recipients. 436 | 437 | Each time you convey a covered work, the recipient automatically 438 | receives a license from the original licensors, to run, modify and 439 | propagate that work, subject to this License. You are not responsible 440 | for enforcing compliance by third parties with this License. 441 | 442 | An "entity transaction" is a transaction transferring control of an 443 | organization, or substantially all assets of one, or subdividing an 444 | organization, or merging organizations. If propagation of a covered 445 | work results from an entity transaction, each party to that 446 | transaction who receives a copy of the work also receives whatever 447 | licenses to the work the party's predecessor in interest had or could 448 | give under the previous paragraph, plus a right to possession of the 449 | Corresponding Source of the work from the predecessor in interest, if 450 | the predecessor has it or can get it with reasonable efforts. 451 | 452 | You may not impose any further restrictions on the exercise of the 453 | rights granted or affirmed under this License. For example, you may 454 | not impose a license fee, royalty, or other charge for exercise of 455 | rights granted under this License, and you may not initiate litigation 456 | (including a cross-claim or counterclaim in a lawsuit) alleging that 457 | any patent claim is infringed by making, using, selling, offering for 458 | sale, or importing the Program or any portion of it. 459 | 460 | 11. Patents. 461 | 462 | A "contributor" is a copyright holder who authorizes use under this 463 | License of the Program or a work on which the Program is based. The 464 | work thus licensed is called the contributor's "contributor version". 465 | 466 | A contributor's "essential patent claims" are all patent claims 467 | owned or controlled by the contributor, whether already acquired or 468 | hereafter acquired, that would be infringed by some manner, permitted 469 | by this License, of making, using, or selling its contributor version, 470 | but do not include claims that would be infringed only as a 471 | consequence of further modification of the contributor version. For 472 | purposes of this definition, "control" includes the right to grant 473 | patent sublicenses in a manner consistent with the requirements of 474 | this License. 475 | 476 | Each contributor grants you a non-exclusive, worldwide, royalty-free 477 | patent license under the contributor's essential patent claims, to 478 | make, use, sell, offer for sale, import and otherwise run, modify and 479 | propagate the contents of its contributor version. 480 | 481 | In the following three paragraphs, a "patent license" is any express 482 | agreement or commitment, however denominated, not to enforce a patent 483 | (such as an express permission to practice a patent or covenant not to 484 | sue for patent infringement). To "grant" such a patent license to a 485 | party means to make such an agreement or commitment not to enforce a 486 | patent against the party. 487 | 488 | If you convey a covered work, knowingly relying on a patent license, 489 | and the Corresponding Source of the work is not available for anyone 490 | to copy, free of charge and under the terms of this License, through a 491 | publicly available network server or other readily accessible means, 492 | then you must either (1) cause the Corresponding Source to be so 493 | available, or (2) arrange to deprive yourself of the benefit of the 494 | patent license for this particular work, or (3) arrange, in a manner 495 | consistent with the requirements of this License, to extend the patent 496 | license to downstream recipients. "Knowingly relying" means you have 497 | actual knowledge that, but for the patent license, your conveying the 498 | covered work in a country, or your recipient's use of the covered work 499 | in a country, would infringe one or more identifiable patents in that 500 | country that you have reason to believe are valid. 501 | 502 | If, pursuant to or in connection with a single transaction or 503 | arrangement, you convey, or propagate by procuring conveyance of, a 504 | covered work, and grant a patent license to some of the parties 505 | receiving the covered work authorizing them to use, propagate, modify 506 | or convey a specific copy of the covered work, then the patent license 507 | you grant is automatically extended to all recipients of the covered 508 | work and works based on it. 509 | 510 | A patent license is "discriminatory" if it does not include within 511 | the scope of its coverage, prohibits the exercise of, or is 512 | conditioned on the non-exercise of one or more of the rights that are 513 | specifically granted under this License. You may not convey a covered 514 | work if you are a party to an arrangement with a third party that is 515 | in the business of distributing software, under which you make payment 516 | to the third party based on the extent of your activity of conveying 517 | the work, and under which the third party grants, to any of the 518 | parties who would receive the covered work from you, a discriminatory 519 | patent license (a) in connection with copies of the covered work 520 | conveyed by you (or copies made from those copies), or (b) primarily 521 | for and in connection with specific products or compilations that 522 | contain the covered work, unless you entered into that arrangement, 523 | or that patent license was granted, prior to 28 March 2007. 524 | 525 | Nothing in this License shall be construed as excluding or limiting 526 | any implied license or other defenses to infringement that may 527 | otherwise be available to you under applicable patent law. 528 | 529 | 12. No Surrender of Others' Freedom. 530 | 531 | If conditions are imposed on you (whether by court order, agreement or 532 | otherwise) that contradict the conditions of this License, they do not 533 | excuse you from the conditions of this License. If you cannot convey a 534 | covered work so as to satisfy simultaneously your obligations under this 535 | License and any other pertinent obligations, then as a consequence you may 536 | not convey it at all. For example, if you agree to terms that obligate you 537 | to collect a royalty for further conveying from those to whom you convey 538 | the Program, the only way you could satisfy both those terms and this 539 | License would be to refrain entirely from conveying the Program. 540 | 541 | 13. Remote Network Interaction; Use with the GNU General Public License. 542 | 543 | Notwithstanding any other provision of this License, if you modify the 544 | Program, your modified version must prominently offer all users 545 | interacting with it remotely through a computer network (if your version 546 | supports such interaction) an opportunity to receive the Corresponding 547 | Source of your version by providing access to the Corresponding Source 548 | from a network server at no charge, through some standard or customary 549 | means of facilitating copying of software. This Corresponding Source 550 | shall include the Corresponding Source for any work covered by version 3 551 | of the GNU General Public License that is incorporated pursuant to the 552 | following paragraph. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the work with which it is combined will remain governed by version 560 | 3 of the GNU General Public License. 561 | 562 | 14. Revised Versions of this License. 563 | 564 | The Free Software Foundation may publish revised and/or new versions of 565 | the GNU Affero General Public License from time to time. Such new versions 566 | will be similar in spirit to the present version, but may differ in detail to 567 | address new problems or concerns. 568 | 569 | Each version is given a distinguishing version number. If the 570 | Program specifies that a certain numbered version of the GNU Affero General 571 | Public License "or any later version" applies to it, you have the 572 | option of following the terms and conditions either of that numbered 573 | version or of any later version published by the Free Software 574 | Foundation. If the Program does not specify a version number of the 575 | GNU Affero General Public License, you may choose any version ever published 576 | by the Free Software Foundation. 577 | 578 | If the Program specifies that a proxy can decide which future 579 | versions of the GNU Affero General Public License can be used, that proxy's 580 | public statement of acceptance of a version permanently authorizes you 581 | to choose that version for the Program. 582 | 583 | Later license versions may give you additional or different 584 | permissions. However, no additional obligations are imposed on any 585 | author or copyright holder as a result of your choosing to follow a 586 | later version. 587 | 588 | 15. Disclaimer of Warranty. 589 | 590 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 591 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 592 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 593 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 594 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 595 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 596 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 597 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 598 | 599 | 16. Limitation of Liability. 600 | 601 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 602 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 603 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 604 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 605 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 606 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 607 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 608 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 609 | SUCH DAMAGES. 610 | 611 | 17. Interpretation of Sections 15 and 16. 612 | 613 | If the disclaimer of warranty and limitation of liability provided 614 | above cannot be given local legal effect according to their terms, 615 | reviewing courts shall apply local law that most closely approximates 616 | an absolute waiver of all civil liability in connection with the 617 | Program, unless a warranty or assumption of liability accompanies a 618 | copy of the Program in return for a fee. 619 | 620 | END OF TERMS AND CONDITIONS 621 | 622 | How to Apply These Terms to Your New Programs 623 | 624 | If you develop a new program, and you want it to be of the greatest 625 | possible use to the public, the best way to achieve this is to make it 626 | free software which everyone can redistribute and change under these terms. 627 | 628 | To do so, attach the following notices to the program. It is safest 629 | to attach them to the start of each source file to most effectively 630 | state the exclusion of warranty; and each file should have at least 631 | the "copyright" line and a pointer to where the full notice is found. 632 | 633 | 634 | Copyright (C) 635 | 636 | This program is free software: you can redistribute it and/or modify 637 | it under the terms of the GNU Affero General Public License as published by 638 | the Free Software Foundation, either version 3 of the License, or 639 | (at your option) any later version. 640 | 641 | This program is distributed in the hope that it will be useful, 642 | but WITHOUT ANY WARRANTY; without even the implied warranty of 643 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 644 | GNU Affero General Public License for more details. 645 | 646 | You should have received a copy of the GNU Affero General Public License 647 | along with this program. If not, see . 648 | 649 | Also add information on how to contact you by electronic and paper mail. 650 | 651 | If your software can interact with users remotely through a computer 652 | network, you should also make sure that it provides a way for users to 653 | get its source. For example, if your program is a web application, its 654 | interface could display a "Source" link that leads users to an archive 655 | of the code. There are many ways you could offer source, and different 656 | solutions will be better for different programs; see section 13 for the 657 | specific requirements. 658 | 659 | You should also get your employer (if you work as a programmer) or school, 660 | if any, to sign a "copyright disclaimer" for the program, if necessary. 661 | For more information on this, and how to apply and follow the GNU AGPL, see 662 | . 663 | 664 | EdX Inc. wishes to state, in clarification of the above license terms, that 665 | any public, independently available web service offered over the network and 666 | communicating with edX's copyrighted works by any form of inter-service 667 | communication, including but not limited to Remote Procedure Call (RPC) 668 | interfaces, is not a work based on our copyrighted work within the meaning 669 | of the license. "Corresponding Source" of this work, or works based on this 670 | work, as defined by the terms of this license do not include source code 671 | files for programs used solely to provide those public, independently 672 | available web services. 673 | 674 | 675 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS 2 | include CHANGELOG.rst 3 | include CONTRIBUTING.rst 4 | include LICENSE.txt 5 | include README.rst 6 | recursive-include fonzie *.html *.png *.gif *js *.css *jpg *jpeg *svg *py 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # GLOBALS 3 | # 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | try: 7 | from urllib import pathname2url 8 | except: 9 | from urllib.request import pathname2url 10 | 11 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 12 | endef 13 | export BROWSER_PYSCRIPT 14 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 15 | 16 | # Get local user ids 17 | USER_ID = $(shell id -u) 18 | GROUP_ID = $(shell id -g) 19 | 20 | # Docker 21 | COMPOSE = USER_ID=$(USER_ID) GROUP_ID=$(GROUP_ID) docker-compose 22 | COMPOSE_RUN = $(COMPOSE) run --rm 23 | COMPOSE_RUN_LMS = $(COMPOSE_RUN) lms 24 | COMPOSE_RUN_FONZIE = $(COMPOSE_RUN) fonzie 25 | COMPOSE_EXEC = $(COMPOSE) exec 26 | 27 | # Django 28 | MANAGE_LMS = $(COMPOSE_EXEC) lms dockerize -wait tcp://mysql:3306 -timeout 60s python manage.py lms 29 | 30 | # -- Python 31 | PYTEST = $(COMPOSE_RUN) -e DJANGO_SETTINGS_MODULE=lms.envs.fun.docker_run_test lms dockerize -wait tcp://mysql:3306 -timeout 60s pytest -c /edx/app/fonzie/setup.cfg 32 | 33 | # 34 | # RULES 35 | # 36 | default: help 37 | 38 | help: ## display this help message 39 | @echo "Please use \`make ' where is one of" 40 | @perl -nle'print $& if m{^[a-zA-Z_-]+:.*?## .*$$}' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m %-25s\033[0m %s\n", $$1, $$2}' 41 | .PHONY: help 42 | 43 | bootstrap: run migrate demo-course ## bootstrap the project 44 | .PHONY: bootstrap 45 | 46 | build: ## build project containers 47 | $(COMPOSE) build lms 48 | .PHONY: build 49 | 50 | clean: ## remove generated byte code, coverage reports, and build artifacts 51 | $(COMPOSE_RUN_FONZIE) find . -name '__pycache__' -exec rm -rf {} + 52 | $(COMPOSE_RUN_FONZIE) find . -name '*.pyc' -exec rm -f {} + 53 | $(COMPOSE_RUN_FONZIE) find . -name '*.pyo' -exec rm -f {} + 54 | $(COMPOSE_RUN_FONZIE) find . -name '*~' -exec rm -f {} + 55 | $(COMPOSE_RUN) --no-deps lms coverage erase 56 | $(COMPOSE_RUN_FONZIE) rm -fr build/ 57 | $(COMPOSE_RUN_FONZIE) rm -fr dist/ 58 | $(COMPOSE_RUN_FONZIE) rm -fr *.egg-info 59 | .PHONY: clean 60 | 61 | coverage: clean ## generate and view HTML coverage report 62 | $(PYTEST) --cov-report html 63 | $(BROWSER) edx-platform/reports/cover/index.html 64 | .PHONY: coverage 65 | 66 | diff_cover: test 67 | $(DIFF-COVER) edx-platform/reports/coverage.xml 68 | .PHONY: diff_cover 69 | 70 | docs: ## generate Sphinx HTML documentation, including API docs 71 | $(COMPOSE_RUN_FONZIE) doc8 --ignore-path docs/_build README.rst docs 72 | rm -f docs/fonzie.rst 73 | rm -f docs/modules.rst 74 | $(COMPOSE_RUN_FONZIE) make -C docs clean 75 | $(COMPOSE_RUN_FONZIE) make -C docs html 76 | $(COMPOSE_RUN_FONZIE) python setup.py check --restructuredtext --strict 77 | $(BROWSER) docs/_build/html/index.html 78 | .PHONY: docs 79 | 80 | migrate: run ## run project migrations 81 | $(MANAGE_LMS) migrate 82 | .PHONY: migrate 83 | 84 | lint: ## check coding style with pycodestyle and pylint 85 | lint: \ 86 | lint-pylint \ 87 | lint-bandit \ 88 | lint-pycodestyle \ 89 | lint-isort \ 90 | lint-pyroma \ 91 | selfcheck 92 | .PHONY: lint 93 | 94 | lint-pylint: ## lint python sources with pylint 95 | @echo "lint:pylint started…" 96 | $(COMPOSE_RUN_FONZIE) pylint fonzie tests 97 | $(COMPOSE_RUN_FONZIE) pylint --py3k fonzie tests 98 | .PHONY: lint-pylint 99 | 100 | lint-bandit: ## lint python sources with bandit 101 | @echo "lint:bandit started…" 102 | @$(COMPOSE_RUN_FONZIE) bandit -qr fonzie 103 | .PHONY: lint-bandit 104 | 105 | lint-pycodestyle: ## lint python sources with pycodestyle 106 | @echo "lint:pycodestyle started…" 107 | $(COMPOSE_RUN_FONZIE) pycodestyle fonzie tests 108 | .PHONY: lint-pycodestyle 109 | 110 | lint-pydocstyle: ## lint python sources with pydocstyle 111 | @echo "lint:pydocstyle started…" 112 | $(COMPOSE_RUN_FONZIE) pydocstyle fonzie tests 113 | .PHONY: lint-pydocstyle 114 | 115 | lint-isort: ## lint python sources with isort 116 | @echo "lint:isort started…" 117 | $(COMPOSE_RUN_FONZIE) isort --check-only --recursive tests fonzie manage.py setup.py 118 | .PHONY: lint-isort 119 | 120 | lint-pyroma: ## lint python sources with pyroma 121 | @echo "lint:pyroma started…" 122 | $(COMPOSE_RUN_FONZIE) pyroma . 123 | .PHONY: lint-pyroma 124 | 125 | 126 | report: ## publish test coverage report 127 | $(COMPOSE_RUN) -e CODECOV_TOKEN lms codecov --commit=${CIRCLE_SHA1} 128 | .PHONY: report 129 | 130 | run: tree ## start lms development server and nginx 131 | $(COMPOSE) up -d nginx 132 | .PHONY: run 133 | 134 | 135 | selfcheck: ## check that the Makefile is well-formed 136 | @echo "The Makefile is well-formed." 137 | .PHONY: selfcheck 138 | 139 | stop: ## stop development server 140 | $(COMPOSE) stop 141 | .PHONY: stop 142 | 143 | test: ## run python tests suite 144 | # Create .pytest_cache directory with appropriate permissions. We cannot 145 | # rely on docker-compose volume as if the directory does not exists, it will 146 | # create one owned by root:root. 147 | @mkdir -p .pytest_cache 148 | $(PYTEST) 149 | .PHONY: test 150 | 151 | test-spec: ## run tests on API specification (API blueprint) 152 | $(COMPOSE_RUN) dredd 153 | .PHONY: test-spec 154 | 155 | validate: lint test ## run tests and linters 156 | .PHONY: validate 157 | 158 | demo-course: ## import demo course from edX repository 159 | @./bin/clone_demo_course 160 | $(COMPOSE_RUN) -v $(shell pwd)/edx-demo-course:/edx/app/edxapp/edx-demo-course cms \ 161 | python manage.py cms import /data /edx/app/edxapp/edx-demo-course 162 | .PHONY: demo-course 163 | 164 | tree: ## create data directories mounted as volumes 165 | bash -c "mkdir -p data/{static,media,export}" 166 | .PHONY: tree 167 | 168 | collectstatic: tree ## copy static assets to static root directory 169 | $(COMPOSE_RUN) lms python manage.py lms collectstatic --noinput --settings=fun.docker_run_development; 170 | $(COMPOSE_RUN) cms python manage.py cms collectstatic --noinput --settings=fun.docker_run_development; 171 | .PHONY: collectstatic 172 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Fonzie, a FUN API for Open edX 2 | ============================== 3 | 4 | |pypi-badge| |travis-badge| |codecov-badge| |doc-badge| |pyversions-badge| 5 | |license-badge| 6 | 7 | **This project is in early-development phase, we are still experimenting on 8 | it.** It is intended to be community-driven, so please, do not hesitate to get 9 | in touch if you have any question related to our implementation or design 10 | decisions. 11 | 12 | Overview 13 | -------- 14 | 15 | Fonzie is an HTTP API over the `Open edX `_ project. It 16 | has been build to open the way to new tools and interfaces in the Open edX 17 | ecosystem. 18 | 19 | We choose to design the API first and then propose an implementation with 20 | Fonzie. The current state of the API schema is available in `API blueprint 21 | format `_; it 22 | can be used to start developing a front-end application that will consume 23 | Fonzie's API (take a look at `API blueprint tools 24 | `_). 25 | 26 | Documentation 27 | ------------- 28 | 29 | The full documentation is at https://fonzie.readthedocs.org. 30 | 31 | License 32 | ------- 33 | 34 | The code in this repository is licensed under the AGPL 3.0 unless otherwise 35 | noted. 36 | 37 | Please see ``LICENSE.txt`` for details. 38 | 39 | How To Contribute 40 | ----------------- 41 | 42 | Contributions are very welcome. 43 | 44 | Even though they were written with ``edx-platform`` in mind, the guidelines 45 | should be followed for Open edX code in general. 46 | 47 | PR description template should be automatically applied if you are sending PR 48 | from github interface; otherwise you can find it it at `PULL_REQUEST_TEMPLATE.md 49 | `_ 50 | 51 | Issue report template should be automatically applied if you are sending it from 52 | github UI as well; otherwise you can find it at `ISSUE_TEMPLATE.md 53 | `_ 54 | 55 | Reporting Security Issues 56 | ------------------------- 57 | 58 | Please do not report security issues in public. Please email security@fun-mooc.fr. 59 | 60 | Getting Help 61 | ------------ 62 | 63 | Have a question about this repository, or about Open edX in general? Please 64 | refer to this `list of resources`_ if you need any assistance. 65 | 66 | .. _list of resources: https://open.edx.org/getting-help 67 | 68 | 69 | .. |pypi-badge| image:: https://img.shields.io/pypi/v/fonzie.svg 70 | :target: https://pypi.python.org/pypi/fonzie/ 71 | :alt: PyPI 72 | 73 | .. |travis-badge| image:: https://travis-ci.org/openfun/fonzie.svg?branch=master 74 | :target: https://travis-ci.org/openfun/fonzie 75 | :alt: Travis 76 | 77 | .. |codecov-badge| image:: http://codecov.io/gh/openfun/fonzie/coverage.svg?branch=master 78 | :target: http://codecov.io/gh/openfun/fonzie?branch=master 79 | :alt: Codecov 80 | 81 | .. |doc-badge| image:: https://readthedocs.org/projects/fonzie/badge/?version=latest 82 | :target: http://fonzie.readthedocs.io/en/latest/ 83 | :alt: Documentation 84 | 85 | .. |pyversions-badge| image:: https://img.shields.io/pypi/pyversions/fonzie.svg 86 | :target: https://pypi.python.org/pypi/fonzie/ 87 | :alt: Supported Python versions 88 | 89 | .. |license-badge| image:: https://img.shields.io/github/license/openfun/fonzie.svg 90 | :target: https://github.com/openfun/fonzie/blob/master/LICENSE.txt 91 | :alt: License 92 | -------------------------------------------------------------------------------- /bin/clone_demo_course: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | RELEASE="${1:-open-release/hawthorn.1}" 4 | 5 | echo -e "📦 Getting edx-demo-course..." 6 | # Clone it from here to get a better message in command line 7 | git clone --depth 1 https://github.com/edx/edx-demo-course.git edx-demo-course 8 | cd edx-demo-course 9 | # Better do the checkout separately from the clone so that if the repository 10 | # already exists, the checkout is still done to the branch we want. 11 | echo -e "✅ Checking out release: ${RELEASE}" 12 | git checkout "${RELEASE}" 13 | git clean -fdx 14 | -------------------------------------------------------------------------------- /bin/pytest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | declare -r USER_ID="$(id -u)" 4 | export USER_ID 5 | declare -r GROUP_ID="$(id -g)" 6 | export GROUP_ID 7 | 8 | docker-compose run --rm \ 9 | -e DJANGO_SETTINGS_MODULE=lms.envs.fun.docker_run_test \ 10 | lms \ 11 | dockerize -wait tcp://mysql:3306 -timeout 60s \ 12 | pytest -c /edx/app/fonzie/setup.cfg "$@" 13 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | enabled: yes 6 | target: auto 7 | patch: 8 | default: 9 | enabled: yes 10 | target: 100% 11 | 12 | comment: false 13 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.2" 2 | 3 | services: 4 | mysql: 5 | image: mysql:5.6 6 | env_file: .env 7 | command: mysqld --character-set-server=utf8 --collation-server=utf8_general_ci 8 | 9 | mongodb: 10 | image: mongo:3.2 11 | # We use WiredTiger in all environments. In development environments we use small files 12 | # to conserve disk space, and disable the journal for a minor performance gain. 13 | # See https://docs.mongodb.com/v3.0/reference/program/mongod/#options for complete details. 14 | command: mongod --smallfiles --nojournal --storageEngine wiredTiger 15 | 16 | memcached: 17 | image: memcached:1.4 18 | 19 | mailcatcher: 20 | image: sj26/mailcatcher:latest 21 | ports: 22 | - "1080:1080" 23 | 24 | # The Open edX LMS service image is build thanks to the project's Dockerfile. 25 | # The resulting image is called 'edxapp-fonzie'. We use Docker volumes to 26 | # override settings (docker_run_development) and redefine URLs to only add 27 | # Fonzie's URLS (see edx-platform/lms/urls.py). 28 | lms: 29 | build: 30 | context: . 31 | args: 32 | USER_ID: ${USER_ID} 33 | GROUP_ID: ${GROUP_ID} 34 | image: edxapp-fonzie 35 | env_file: .env 36 | ports: 37 | - "8072:8000" 38 | volumes: 39 | # Override installed app with the app source directory to provide an 40 | # editable environment 41 | - ./setup.cfg:/edx/app/fonzie/setup.cfg 42 | - ./fonzie:/edx/app/fonzie/fonzie 43 | - ./tests:/edx/app/fonzie/tests 44 | - ./data:/edx/app/edxapp/data 45 | - ./edx-platform/config/lms/docker_run_development.py:/config/lms/docker_run_development.py 46 | - ./edx-platform/config/lms/docker_run_test.py:/config/lms/docker_run_test.py 47 | - ./edx-platform/reports:/edx/app/edxapp/edx-platform/reports 48 | - .pytest_cache:/edx/app/edxapp/edx-platform/.pytest_cache 49 | # We use dockerize to wait for the database to be up before running django 50 | # development server 51 | command: > 52 | dockerize -wait tcp://mysql:3306 -timeout 60s 53 | python manage.py lms runserver 0.0.0.0:8000 --settings=fun.docker_run_development 54 | depends_on: 55 | - mailcatcher 56 | - mysql 57 | - mongodb 58 | - memcached 59 | 60 | cms: 61 | image: edxapp-fonzie 62 | env_file: .env 63 | environment: 64 | SERVICE_VARIANT: cms 65 | DJANGO_SETTINGS_MODULE: cms.envs.fun.docker_run_development 66 | volumes: 67 | - ./edx-platform/config/cms/docker_run_development.py:/config/cms/docker_run_development.py 68 | - ./data:/edx/app/edxapp/data 69 | depends_on: 70 | - lms 71 | user: ${UID}:${GID} 72 | 73 | 74 | # The fonzie service uses the image built for the lms service. We only 75 | # redefine the working_dir to use the container as a mean to run tests and 76 | # code quality checking on the Fonzie application itself. 77 | fonzie: 78 | image: edxapp-fonzie 79 | volumes: 80 | - .:/edx/app/fonzie 81 | working_dir: /edx/app/fonzie 82 | 83 | dredd: 84 | image: apiaryio/dredd:latest 85 | working_dir: /api 86 | volumes: 87 | - .:/api 88 | depends_on: 89 | - lms 90 | 91 | nginx: 92 | image: nginx:1.13 93 | ports: 94 | - "8073:8080" 95 | volumes: 96 | - ./docker/files/etc/nginx/conf.d:/etc/nginx/conf.d:ro 97 | - ./data:/data:ro 98 | depends_on: 99 | - lms 100 | - cms 101 | -------------------------------------------------------------------------------- /docker/files/etc/nginx/conf.d/lms.conf: -------------------------------------------------------------------------------- 1 | upstream lms-backend { 2 | server lms:8000 fail_timeout=0; 3 | } 4 | 5 | server { 6 | listen 8080; 7 | server_name localhost; 8 | 9 | # Prevent invalid display courseware in IE 10+ with high privacy settings 10 | add_header P3P 'CP="Open edX does not have a P3P policy."'; 11 | 12 | client_max_body_size 4M; 13 | 14 | rewrite ^(.*)/favicon.ico$ /static/images/favicon.ico last; 15 | 16 | # Disables server version feedback on pages and in headers 17 | server_tokens off; 18 | 19 | location @proxy_to_lms_app { 20 | proxy_set_header X-Forwarded-Proto $scheme; 21 | proxy_set_header X-Forwarded-Port $server_port; 22 | proxy_set_header X-Forwarded-For $remote_addr; 23 | 24 | proxy_set_header Host $http_host; 25 | 26 | proxy_redirect off; 27 | proxy_pass http://lms-backend; 28 | } 29 | 30 | location / { 31 | try_files $uri @proxy_to_lms_app; 32 | } 33 | 34 | # /login?next= can be used by 3rd party sites in tags to 35 | # determine whether a user on their site is logged into edX. 36 | # The most common image to use is favicon.ico. 37 | location /login { 38 | if ( $arg_next ~* "favicon.ico" ) { 39 | return 403; 40 | } 41 | try_files $uri @proxy_to_lms_app; 42 | } 43 | 44 | # Need a separate location for the image uploads endpoint to limit upload sizes 45 | location ~ ^/api/profile_images/[^/]*/[^/]*/upload$ { 46 | try_files $uri @proxy_to_lms_app; 47 | client_max_body_size 1049576; 48 | } 49 | 50 | location ~ ^/media/(?P.*) { 51 | root /data/media; 52 | try_files /$file =404; 53 | expires 31536000s; 54 | } 55 | 56 | location ~ ^/static/(?P.*) { 57 | root /data/static; 58 | try_files /$file =404; 59 | } 60 | 61 | location ~ ^/restricted/(?P.*) { 62 | root /data/export; 63 | try_files /$file =404; 64 | internal; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help 23 | help: 24 | @echo "Please use \`make ' where is one of" 25 | @echo " html to make standalone HTML files" 26 | @echo " dirhtml to make HTML files named index.html in directories" 27 | @echo " singlehtml to make a single large HTML file" 28 | @echo " pickle to make pickle files" 29 | @echo " json to make JSON files" 30 | @echo " htmlhelp to make HTML files and a HTML help project" 31 | @echo " qthelp to make HTML files and a qthelp project" 32 | @echo " applehelp to make an Apple Help Book" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " epub3 to make an epub3" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | @echo " dummy to check syntax errors of document sources" 51 | 52 | .PHONY: clean 53 | clean: 54 | rm -rf $(BUILDDIR)/* 55 | 56 | .PHONY: html 57 | html: 58 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 61 | 62 | .PHONY: dirhtml 63 | dirhtml: 64 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 65 | @echo 66 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 67 | 68 | .PHONY: singlehtml 69 | singlehtml: 70 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 71 | @echo 72 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 73 | 74 | .PHONY: pickle 75 | pickle: 76 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 77 | @echo 78 | @echo "Build finished; now you can process the pickle files." 79 | 80 | .PHONY: json 81 | json: 82 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 83 | @echo 84 | @echo "Build finished; now you can process the JSON files." 85 | 86 | .PHONY: htmlhelp 87 | htmlhelp: 88 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 89 | @echo 90 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 91 | ".hhp project file in $(BUILDDIR)/htmlhelp." 92 | 93 | .PHONY: qthelp 94 | qthelp: 95 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 96 | @echo 97 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 98 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 99 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/fonzie.qhcp" 100 | @echo "To view the help file:" 101 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/fonzie.qhc" 102 | 103 | .PHONY: applehelp 104 | applehelp: 105 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 106 | @echo 107 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 108 | @echo "N.B. You won't be able to view it unless you put it in" \ 109 | "~/Library/Documentation/Help or install it in your application" \ 110 | "bundle." 111 | 112 | .PHONY: devhelp 113 | devhelp: 114 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 115 | @echo 116 | @echo "Build finished." 117 | @echo "To view the help file:" 118 | @echo "# mkdir -p $$HOME/.local/share/devhelp/fonzie" 119 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/fonzie" 120 | @echo "# devhelp" 121 | 122 | .PHONY: epub 123 | epub: 124 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 125 | @echo 126 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 127 | 128 | .PHONY: epub3 129 | epub3: 130 | $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 131 | @echo 132 | @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." 133 | 134 | .PHONY: latex 135 | latex: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo 138 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 139 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 140 | "(use \`make latexpdf' here to do that automatically)." 141 | 142 | .PHONY: latexpdf 143 | latexpdf: 144 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 145 | @echo "Running LaTeX files through pdflatex..." 146 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 147 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 148 | 149 | .PHONY: latexpdfja 150 | latexpdfja: 151 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 152 | @echo "Running LaTeX files through platex and dvipdfmx..." 153 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 154 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 155 | 156 | .PHONY: text 157 | text: 158 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 159 | @echo 160 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 161 | 162 | .PHONY: man 163 | man: 164 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 165 | @echo 166 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 167 | 168 | .PHONY: texinfo 169 | texinfo: 170 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 171 | @echo 172 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 173 | @echo "Run \`make' in that directory to run these through makeinfo" \ 174 | "(use \`make info' here to do that automatically)." 175 | 176 | .PHONY: info 177 | info: 178 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 179 | @echo "Running Texinfo files through makeinfo..." 180 | make -C $(BUILDDIR)/texinfo info 181 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 182 | 183 | .PHONY: gettext 184 | gettext: 185 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 186 | @echo 187 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 188 | 189 | .PHONY: changes 190 | changes: 191 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 192 | @echo 193 | @echo "The overview file is in $(BUILDDIR)/changes." 194 | 195 | .PHONY: linkcheck 196 | linkcheck: 197 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 198 | @echo 199 | @echo "Link check complete; look for any errors in the above output " \ 200 | "or in $(BUILDDIR)/linkcheck/output.txt." 201 | 202 | .PHONY: doctest 203 | doctest: 204 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 205 | @echo "Testing of doctests in the sources finished, look at the " \ 206 | "results in $(BUILDDIR)/doctest/output.txt." 207 | 208 | .PHONY: coverage 209 | coverage: 210 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 211 | @echo "Testing of coverage in the sources finished, look at the " \ 212 | "results in $(BUILDDIR)/coverage/python.txt." 213 | 214 | .PHONY: xml 215 | xml: 216 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 217 | @echo 218 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 219 | 220 | .PHONY: pseudoxml 221 | pseudoxml: 222 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 223 | @echo 224 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 225 | 226 | .PHONY: dummy 227 | dummy: 228 | $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy 229 | @echo 230 | @echo "Build finished. Dummy builder generates no files." 231 | -------------------------------------------------------------------------------- /docs/_static/theme_overrides.css: -------------------------------------------------------------------------------- 1 | /* override table width restrictions */ 2 | .wy-table-responsive table td, .wy-table-responsive table th { 3 | /* !important prevents the common CSS stylesheets from 4 | overriding this as on RTD they are loaded after this stylesheet */ 5 | white-space: normal !important; 6 | } 7 | 8 | .wy-table-responsive { 9 | overflow: visible !important; 10 | } 11 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # pylint: disable=invalid-name 3 | """ 4 | fonzie documentation build configuration file. 5 | 6 | This file is execfile()d with the current directory set to its 7 | containing dir. 8 | 9 | Note that not all possible configuration values are present in this 10 | autogenerated file. 11 | 12 | All configuration values have a default; values that are commented out 13 | serve to show the default. 14 | """ 15 | 16 | from __future__ import absolute_import, unicode_literals 17 | 18 | import os 19 | import re 20 | import sys 21 | from subprocess import check_call 22 | 23 | import edx_theme 24 | 25 | import django 26 | from django.conf import settings 27 | from django.utils import six 28 | 29 | from ..fonzie import __version__ as package_version 30 | 31 | REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 32 | sys.path.append(REPO_ROOT) 33 | 34 | VERSION = package_version 35 | 36 | # Configure Django for autodoc usage 37 | settings.configure() 38 | django.setup() 39 | 40 | # If extensions (or modules to document with autodoc) are in another directory, 41 | # add these directories to sys.path here. If the directory is relative to the 42 | # documentation root, use os.path.abspath to make it absolute, like shown here. 43 | # 44 | # import os 45 | # import sys 46 | # sys.path.insert(0, os.path.abspath('.')) 47 | 48 | # -- General configuration ------------------------------------------------ 49 | 50 | # If your documentation needs a minimal Sphinx version, state it here. 51 | # 52 | needs_sphinx = '1.3' 53 | 54 | # Add any Sphinx extension module names here, as strings. They can be 55 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 56 | # ones. 57 | extensions = [ 58 | 'edx_theme', 59 | 'sphinx.ext.autodoc', 60 | 'sphinx.ext.doctest', 61 | 'sphinx.ext.intersphinx', 62 | 'sphinx.ext.ifconfig', 63 | 'sphinx.ext.napoleon' 64 | ] 65 | 66 | # A list of warning types to suppress arbitrary warning messages. 67 | suppress_warnings = [ 68 | 'image.nonlocal_uri', 69 | ] 70 | 71 | # Add any paths that contain templates here, relative to this directory. 72 | templates_path = ['_templates'] 73 | 74 | # The suffix(es) of source filenames. 75 | # You can specify multiple suffix as a list of string: 76 | # 77 | # source_suffix = ['.rst', '.md'] 78 | source_suffix = '.rst' 79 | 80 | # The encoding of source files. 81 | # 82 | # source_encoding = 'utf-8-sig' 83 | 84 | # The master toctree document. 85 | master_doc = 'index' 86 | 87 | # General information about the project. 88 | project = 'fonzie' 89 | copyright = edx_theme.COPYRIGHT # pylint: disable=redefined-builtin 90 | author = edx_theme.AUTHOR 91 | project_title = 'Fonzie' 92 | documentation_title = "{project_title}".format(project_title=project_title) 93 | 94 | # The version info for the project you're documenting, acts as replacement for 95 | # |version| and |release|, also used in various other places throughout the 96 | # built documents. 97 | # 98 | # The short X.Y version. 99 | version = VERSION 100 | # The full version, including alpha/beta/rc tags. 101 | release = VERSION 102 | 103 | # The language for content autogenerated by Sphinx. Refer to documentation 104 | # for a list of supported languages. 105 | # 106 | # This is also used if you do content translation via gettext catalogs. 107 | # Usually you set "language" from the command line for these cases. 108 | language = None 109 | 110 | # There are two options for replacing |today|: either, you set today to some 111 | # non-false value, then it is used: 112 | # 113 | # today = '' 114 | # 115 | # Else, today_fmt is used as the format for a strftime call. 116 | # 117 | # today_fmt = '%B %d, %Y' 118 | 119 | # List of patterns, relative to source directory, that match files and 120 | # directories to ignore when looking for source files. 121 | # This patterns also effect to html_static_path and html_extra_path 122 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 123 | 124 | # The reST default role (used for this markup: `text`) to use for all 125 | # documents. 126 | # 127 | # default_role = None 128 | 129 | # If true, '()' will be appended to :func: etc. cross-reference text. 130 | # 131 | # add_function_parentheses = True 132 | 133 | # If true, the current module name will be prepended to all description 134 | # unit titles (such as .. function::). 135 | # 136 | # add_module_names = True 137 | 138 | # If true, sectionauthor and moduleauthor directives will be shown in the 139 | # output. They are ignored by default. 140 | # 141 | # show_authors = False 142 | 143 | # The name of the Pygments (syntax highlighting) style to use. 144 | pygments_style = 'sphinx' 145 | 146 | # A list of ignored prefixes for module index sorting. 147 | # modindex_common_prefix = [] 148 | 149 | # If true, keep warnings as "system message" paragraphs in the built documents. 150 | # keep_warnings = False 151 | 152 | # If true, `todo` and `todoList` produce output, else they produce nothing. 153 | todo_include_todos = False 154 | 155 | 156 | # -- Options for HTML output ---------------------------------------------- 157 | 158 | # The theme to use for HTML and HTML Help pages. See the documentation for 159 | # a list of builtin themes. 160 | 161 | html_theme = 'edx_theme' 162 | 163 | # Theme options are theme-specific and customize the look and feel of a theme 164 | # further. For a list of options available for each theme, see the 165 | # documentation. 166 | # 167 | # html_theme_options = {} 168 | 169 | # Add any paths that contain custom themes here, relative to this directory. 170 | html_theme_path = [edx_theme.get_html_theme_path()] 171 | 172 | # The name for this set of Sphinx documents. 173 | # " v documentation" by default. 174 | # 175 | # html_title = 'fonzie v0.7.0' 176 | 177 | # A shorter title for the navigation bar. Default is the same as html_title. 178 | # 179 | # html_short_title = None 180 | 181 | # The name of an image file (relative to this directory) to place at the top 182 | # of the sidebar. 183 | # 184 | # html_logo = None 185 | 186 | # The name of an image file (relative to this directory) to use as a favicon of 187 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 188 | # pixels large. 189 | # 190 | # html_favicon = None 191 | 192 | # Add any paths that contain custom static files (such as style sheets) here, 193 | # relative to this directory. They are copied after the builtin static files, 194 | # so a file named "default.css" will overwrite the builtin "default.css". 195 | html_static_path = ['_static'] 196 | 197 | # Add any extra paths that contain custom files (such as robots.txt or 198 | # .htaccess) here, relative to this directory. These files are copied 199 | # directly to the root of the documentation. 200 | # 201 | # html_extra_path = [] 202 | 203 | # If not None, a 'Last updated on:' timestamp is inserted at every page 204 | # bottom, using the given strftime format. 205 | # The empty string is equivalent to '%b %d, %Y'. 206 | # 207 | # html_last_updated_fmt = None 208 | 209 | # If true, SmartyPants will be used to convert quotes and dashes to 210 | # typographically correct entities. 211 | # 212 | # html_use_smartypants = True 213 | 214 | # Custom sidebar templates, maps document names to template names. 215 | # 216 | # html_sidebars = {} 217 | 218 | # Additional templates that should be rendered to pages, maps page names to 219 | # template names. 220 | # 221 | # html_additional_pages = {} 222 | 223 | # If false, no module index is generated. 224 | # 225 | # html_domain_indices = True 226 | 227 | # If false, no index is generated. 228 | # 229 | # html_use_index = True 230 | 231 | # If true, the index is split into individual pages for each letter. 232 | # 233 | # html_split_index = False 234 | 235 | # If true, links to the reST sources are added to the pages. 236 | # 237 | # html_show_sourcelink = True 238 | 239 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 240 | # 241 | # html_show_sphinx = True 242 | 243 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 244 | # 245 | # html_show_copyright = True 246 | 247 | # If true, an OpenSearch description file will be output, and all pages will 248 | # contain a tag referring to it. The value of this option must be the 249 | # base URL from which the finished HTML is served. 250 | # 251 | # html_use_opensearch = '' 252 | 253 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 254 | # html_file_suffix = None 255 | 256 | # Language to be used for generating the HTML full-text search index. 257 | # Sphinx supports the following languages: 258 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 259 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' 260 | # 261 | # html_search_language = 'en' 262 | 263 | # A dictionary with options for the search language support, empty by default. 264 | # 'ja' uses this config value. 265 | # 'zh' user can custom change `jieba` dictionary path. 266 | # 267 | # html_search_options = {'type': 'default'} 268 | 269 | # The name of a javascript file (relative to the configuration directory) that 270 | # implements a search results scorer. If empty, the default will be used. 271 | # 272 | # html_search_scorer = 'scorer.js' 273 | 274 | # Output file base name for HTML help builder. 275 | htmlhelp_basename = '{project_name}doc'.format(project_name=project) 276 | 277 | # -- Options for LaTeX output --------------------------------------------- 278 | 279 | latex_elements = { 280 | # The paper size ('letterpaper' or 'a4paper'). 281 | # 282 | # 'papersize': 'letterpaper', 283 | 284 | # The font size ('10pt', '11pt' or '12pt'). 285 | # 286 | # 'pointsize': '10pt', 287 | 288 | # Additional stuff for the LaTeX preamble. 289 | # 290 | # 'preamble': '', 291 | 292 | # Latex figure (float) alignment 293 | # 294 | # 'figure_align': 'htbp', 295 | } 296 | 297 | # Grouping the document tree into LaTeX files. List of tuples 298 | # (source start file, target name, title, 299 | # author, documentclass [howto, manual, or own class]). 300 | latex_target = '{project}.tex'.format(project=project) 301 | latex_documents = [ 302 | (master_doc, latex_target, documentation_title, 303 | author, 'manual'), 304 | ] 305 | 306 | # The name of an image file (relative to this directory) to place at the top of 307 | # the title page. 308 | # 309 | # latex_logo = None 310 | 311 | # For "manual" documents, if this is true, then toplevel headings are parts, 312 | # not chapters. 313 | # 314 | # latex_use_parts = False 315 | 316 | # If true, show page references after internal links. 317 | # 318 | # latex_show_pagerefs = False 319 | 320 | # If true, show URL addresses after external links. 321 | # 322 | # latex_show_urls = False 323 | 324 | # Documents to append as an appendix to all manuals. 325 | # 326 | # latex_appendices = [] 327 | 328 | # It false, will not define \strong, \code, itleref, \crossref ... but only 329 | # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added 330 | # packages. 331 | # 332 | # latex_keep_old_macro_names = True 333 | 334 | # If false, no module index is generated. 335 | # 336 | # latex_domain_indices = True 337 | 338 | 339 | # -- Options for manual page output --------------------------------------- 340 | 341 | # One entry per manual page. List of tuples 342 | # (source start file, name, description, authors, manual section). 343 | man_pages = [ 344 | (master_doc, project_title, documentation_title, 345 | [author], 1) 346 | ] 347 | 348 | # If true, show URL addresses after external links. 349 | # 350 | # man_show_urls = False 351 | 352 | 353 | # -- Options for Texinfo output ------------------------------------------- 354 | 355 | # Grouping the document tree into Texinfo files. List of tuples 356 | # (source start file, target name, title, author, 357 | # dir menu entry, description, category) 358 | texinfo_documents = [ 359 | (master_doc, project_title, documentation_title, 360 | author, project_title, 'An FUN API for Open edX', 361 | 'Miscellaneous'), 362 | ] 363 | 364 | # Documents to append as an appendix to all manuals. 365 | # 366 | # texinfo_appendices = [] 367 | 368 | # If false, no module index is generated. 369 | # 370 | # texinfo_domain_indices = True 371 | 372 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 373 | # 374 | # texinfo_show_urls = 'footnote' 375 | 376 | # If true, do not generate a @detailmenu in the "Top" node's menu. 377 | # 378 | # texinfo_no_detailmenu = False 379 | 380 | 381 | # -- Options for Epub output ---------------------------------------------- 382 | 383 | # Bibliographic Dublin Core info. 384 | epub_title = project 385 | epub_author = author 386 | epub_publisher = author 387 | epub_copyright = copyright 388 | 389 | # The basename for the epub file. It defaults to the project name. 390 | # epub_basename = project 391 | 392 | # The HTML theme for the epub output. Since the default themes are not 393 | # optimized for small screen space, using the same theme for HTML and epub 394 | # output is usually not wise. This defaults to 'epub', a theme designed to save 395 | # visual space. 396 | # 397 | # epub_theme = 'epub' 398 | 399 | # The language of the text. It defaults to the language option 400 | # or 'en' if the language is not set. 401 | # 402 | # epub_language = '' 403 | 404 | # The scheme of the identifier. Typical schemes are ISBN or URL. 405 | # epub_scheme = '' 406 | 407 | # The unique identifier of the text. This can be a ISBN number 408 | # or the project homepage. 409 | # 410 | # epub_identifier = '' 411 | 412 | # A unique identification for the text. 413 | # 414 | # epub_uid = '' 415 | 416 | # A tuple containing the cover image and cover page html template filenames. 417 | # 418 | # epub_cover = () 419 | 420 | # A sequence of (type, uri, title) tuples for the guide element of content.opf. 421 | # 422 | # epub_guide = () 423 | 424 | # HTML files that should be inserted before the pages created by sphinx. 425 | # The format is a list of tuples containing the path and title. 426 | # 427 | # epub_pre_files = [] 428 | 429 | # HTML files that should be inserted after the pages created by sphinx. 430 | # The format is a list of tuples containing the path and title. 431 | # 432 | # epub_post_files = [] 433 | 434 | # A list of files that should not be packed into the epub file. 435 | epub_exclude_files = ['search.html'] 436 | 437 | # The depth of the table of contents in toc.ncx. 438 | # 439 | # epub_tocdepth = 3 440 | 441 | # Allow duplicate toc entries. 442 | # 443 | # epub_tocdup = True 444 | 445 | # Choose between 'default' and 'includehidden'. 446 | # 447 | # epub_tocscope = 'default' 448 | 449 | # Fix unsupported image types using the Pillow. 450 | # 451 | # epub_fix_images = False 452 | 453 | # Scale large images. 454 | # 455 | # epub_max_image_width = 0 456 | 457 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 458 | # 459 | # epub_show_urls = 'inline' 460 | 461 | # If false, no index is generated. 462 | # 463 | # epub_use_index = True 464 | 465 | 466 | # Example configuration for intersphinx: refer to the Python standard library. 467 | intersphinx_mapping = { 468 | 'python': ('https://docs.python.org/3.6', None), 469 | 'django': ('https://docs.djangoproject.com/en/1.11/', 'https://docs.djangoproject.com/en/1.11/_objects/'), 470 | 'model_utils': ('https://django-model-utils.readthedocs.io/en/latest/', None), 471 | } 472 | 473 | 474 | def on_init(app): # pylint: disable=unused-argument 475 | """ 476 | Run sphinx-apidoc after Sphinx initialization. 477 | 478 | Read the Docs won't run tox or custom shell commands, so we need this to 479 | avoid checking in the generated reStructuredText files. 480 | """ 481 | docs_path = os.path.abspath(os.path.dirname(__file__)) 482 | root_path = os.path.abspath(os.path.join(docs_path, '..')) 483 | apidoc_path = 'sphinx-apidoc' 484 | if hasattr(sys, 'real_prefix'): # Check to see if we are in a virtualenv 485 | # If we are, assemble the path manually 486 | bin_path = os.path.abspath(os.path.join(sys.prefix, 'bin')) 487 | apidoc_path = os.path.join(bin_path, apidoc_path) 488 | check_call([apidoc_path, '-o', docs_path, os.path.join(root_path, 'fonzie'), 489 | os.path.join(root_path, 'fonzie/migrations')]) 490 | 491 | 492 | def setup(app): 493 | """Sphinx extension: run sphinx-apidoc.""" 494 | event = 'builder-inited' if six.PY3 else b'builder-inited' 495 | app.connect(event, on_init) 496 | -------------------------------------------------------------------------------- /docs/developer_guide.rst: -------------------------------------------------------------------------------- 1 | Developer guide 2 | =============== 3 | 4 | Fonzie's project has been designed with "container-native development" in mind. 5 | In this guide, we will consider that your are familiar with Docker and its 6 | ecosystem. 7 | 8 | 9 | Pre-requisites for Fonzie contributors 10 | -------------------------------------- 11 | 12 | As you might already have expected, we extensively use `Docker 13 | `_ (17.12+) and `Docker compose 14 | `_ (1.18+) in our development 15 | workflow. So, if you intend to work on Fonzie, please, make sure they are both 16 | installed and functional before pursuing this guide. 17 | 18 | 19 | Getting started with the development stack 20 | ------------------------------------------ 21 | 22 | First things first, to start contributing on the project, you will need to clone 23 | Fonzie's repository and then build the Docker-based development stack with: 24 | 25 | .. code-block:: bash 26 | 27 | # Clone Fonzie's repository 28 | $ git clone git@github.com:openfun/fonzie.git 29 | 30 | # Build required containers 31 | $ make bootstrap 32 | 33 | 34 | The ``make bootstrap`` command will: 35 | 36 | - build an ``edx-platform`` Docker image with Fonzie installed and configured, 37 | - run the development server 38 | - perform Open edx LMS database migrations. 39 | 40 | Fonzie's API should be accessible from: 41 | `http://localhost:8072/api/v1.0/ 42 | `_ 43 | 44 | We invite you to test the ``status/version`` endpoint to ensure everything went 45 | well: 46 | 47 | .. code-block:: bash 48 | 49 | $ curl http://www.mydomain.com:8080/api/v1.0/status/version 50 | {"version":"0.7.0"} 51 | 52 | 53 | Development workflow 54 | -------------------- 55 | 56 | If you use to work with Django's development server, you will have the same 57 | experience with a Docker-based development stack: Fonzie is installed in edit 58 | mode and mounted as a volume in the ``lms`` service. Combined with the 59 | development server, the application will be reloaded when the source code 60 | changes. 61 | 62 | Adding dependencies 63 | ------------------- 64 | 65 | If your work on Fonzie requires new dependencies, you will need to: 66 | 67 | 1. Update the ``options.install_requires`` section of the ``setup.cfg`` file (or 68 | the ``options.extras_require.[dev,doc,...]`` section if it's a development 69 | dependency), 70 | 2. Rebuild the ``lms`` service image via the ``make build`` utility. 71 | 3. Optionally perform required database migrations via: 72 | 73 | .. code-block:: bash 74 | 75 | $ make migrate 76 | -------------------------------------------------------------------------------- /docs/getting_started.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | 4 | Fonzie is supposed to be installed as a Django application running in an Open 5 | edX platform instance. Fonzie has been designed to be integrated with the 6 | current Open edX release (Ginkgo at the time of writing). We plan to support the 7 | next Open edX release (Hawthorn) in a near future. 8 | 9 | Install dependencies 10 | -------------------- 11 | 12 | Install fonzie and its dependencies in your Open edX installation with ``pip``: 13 | 14 | .. code-block:: bash 15 | 16 | $ (sudo) pip install fonzie 17 | 18 | 19 | Configure Fonzie 20 | ---------------- 21 | 22 | Once installed, Fonzie needs to be configured as a standard Django application, 23 | *i.e.* adding ``fonzie`` to your ``INSTALLED_APPS`` and Fonzie's URLs to your 24 | project's URLs. 25 | 26 | Open edX settings 27 | ^^^^^^^^^^^^^^^^^ 28 | 29 | Edit the LMS settings of your Open edX instance (*e.g.* ``lms/envs/private.py``) by adding 30 | ``fonzie`` to your ``INSTALLED_APPS`` and configure Django Rest Framework (_aka_ 31 | DRF) API versioning support as follows: 32 | 33 | .. code-block:: python 34 | 35 | # lms/env/private.py 36 | INSTALLED_APPS += ( 37 | 'fonzie', 38 | ) 39 | 40 | # Django Rest Framework (aka DRF) 41 | REST_FRAMEWORK.update({ 42 | 'ALLOWED_VERSIONS': ('1.0', ), 43 | 'DEFAULT_VERSION': '1.0', 44 | 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning', 45 | }) 46 | 47 | 48 | Open edX URLs 49 | ^^^^^^^^^^^^^ 50 | 51 | Add Fonzie's urls in Open edX LMS's URLs: 52 | 53 | .. code-block:: python 54 | 55 | # lms/fonzie_urls.py 56 | from lms.urls import * 57 | 58 | urlpatterns += [ 59 | # [...] 60 | url(r'^api/', include('fonzie.urls')), 61 | ] 62 | 63 | 64 | Test your installation 65 | ---------------------- 66 | 67 | Now that we've installed and configured Fonzie, it's time to test that our API 68 | is responding! If we consider that installed Open edX LMS is served from 69 | ``www.mydomain.com`` on the port ``8080``, we can use the ``http`` tool (see `HTTPie 70 | project `_) to query the API: 71 | 72 | 73 | .. code-block:: bash 74 | 75 | $ http http://www.mydomain.com:8080/api/v1.0/status/version 76 | 77 | # http command output 78 | HTTP/1.0 200 OK 79 | Allow: GET, HEAD, OPTIONS 80 | Content-Language: en 81 | Content-Type: application/json 82 | Date: Mon, 28 May 2018 15:37:02 GMT 83 | Server: WSGIServer/0.1 Python/2.7.12 84 | Vary: Accept, Accept-Language, Cookie 85 | X-Frame-Options: ALLOW 86 | 87 | { 88 | "version": "0.7.0" 89 | } 90 | 91 | 92 | Alternatively, you can use ``curl``: 93 | 94 | 95 | .. code-block:: bash 96 | 97 | $ curl http://www.mydomain.com:8080/api/v1.0/status/version 98 | {"version":"0.7.0"} 99 | 100 | 101 | The output of this command should be a JSON payload containing the running 102 | version of Fonzie. 103 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. fonzie documentation master file, created by 2 | sphinx-quickstart on Wed Feb 14 15:07:23 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | fonzie 7 | ====== 8 | An FUN API for Open edX 9 | 10 | Contents: 11 | 12 | .. toctree:: 13 | :maxdepth: 2 14 | 15 | readme 16 | getting_started 17 | developer_guide 18 | testing 19 | modules 20 | 21 | 22 | Indices and tables 23 | ================== 24 | 25 | * :ref:`genindex` 26 | * :ref:`modindex` 27 | * :ref:`search` 28 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. epub3 to make an epub3 31 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 32 | echo. text to make text files 33 | echo. man to make manual pages 34 | echo. texinfo to make Texinfo files 35 | echo. gettext to make PO message catalogs 36 | echo. changes to make an overview over all changed/added/deprecated items 37 | echo. xml to make Docutils-native XML files 38 | echo. pseudoxml to make pseudoxml-XML files for display purposes 39 | echo. linkcheck to check all external links for integrity 40 | echo. doctest to run all doctests embedded in the documentation if enabled 41 | echo. coverage to run coverage check of the documentation if enabled 42 | echo. dummy to check syntax errors of document sources 43 | goto end 44 | ) 45 | 46 | if "%1" == "clean" ( 47 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 48 | del /q /s %BUILDDIR%\* 49 | goto end 50 | ) 51 | 52 | 53 | REM Check if sphinx-build is available and fallback to Python version if any 54 | %SPHINXBUILD% 1>NUL 2>NUL 55 | if errorlevel 9009 goto sphinx_python 56 | goto sphinx_ok 57 | 58 | :sphinx_python 59 | 60 | set SPHINXBUILD=python -m sphinx.__init__ 61 | %SPHINXBUILD% 2> nul 62 | if errorlevel 9009 ( 63 | echo. 64 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 65 | echo.installed, then set the SPHINXBUILD environment variable to point 66 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 67 | echo.may add the Sphinx directory to PATH. 68 | echo. 69 | echo.If you don't have Sphinx installed, grab it from 70 | echo.http://sphinx-doc.org/ 71 | exit /b 1 72 | ) 73 | 74 | :sphinx_ok 75 | 76 | 77 | if "%1" == "html" ( 78 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 79 | if errorlevel 1 exit /b 1 80 | echo. 81 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 82 | goto end 83 | ) 84 | 85 | if "%1" == "dirhtml" ( 86 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 87 | if errorlevel 1 exit /b 1 88 | echo. 89 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 90 | goto end 91 | ) 92 | 93 | if "%1" == "singlehtml" ( 94 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 95 | if errorlevel 1 exit /b 1 96 | echo. 97 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 98 | goto end 99 | ) 100 | 101 | if "%1" == "pickle" ( 102 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 103 | if errorlevel 1 exit /b 1 104 | echo. 105 | echo.Build finished; now you can process the pickle files. 106 | goto end 107 | ) 108 | 109 | if "%1" == "json" ( 110 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 111 | if errorlevel 1 exit /b 1 112 | echo. 113 | echo.Build finished; now you can process the JSON files. 114 | goto end 115 | ) 116 | 117 | if "%1" == "htmlhelp" ( 118 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 119 | if errorlevel 1 exit /b 1 120 | echo. 121 | echo.Build finished; now you can run HTML Help Workshop with the ^ 122 | .hhp project file in %BUILDDIR%/htmlhelp. 123 | goto end 124 | ) 125 | 126 | if "%1" == "qthelp" ( 127 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 128 | if errorlevel 1 exit /b 1 129 | echo. 130 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 131 | .qhcp project file in %BUILDDIR%/qthelp, like this: 132 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\fonzie.qhcp 133 | echo.To view the help file: 134 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\fonzie.ghc 135 | goto end 136 | ) 137 | 138 | if "%1" == "devhelp" ( 139 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 140 | if errorlevel 1 exit /b 1 141 | echo. 142 | echo.Build finished. 143 | goto end 144 | ) 145 | 146 | if "%1" == "epub" ( 147 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 148 | if errorlevel 1 exit /b 1 149 | echo. 150 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 151 | goto end 152 | ) 153 | 154 | if "%1" == "epub3" ( 155 | %SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3 156 | if errorlevel 1 exit /b 1 157 | echo. 158 | echo.Build finished. The epub3 file is in %BUILDDIR%/epub3. 159 | goto end 160 | ) 161 | 162 | if "%1" == "latex" ( 163 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 164 | if errorlevel 1 exit /b 1 165 | echo. 166 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdf" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "latexpdfja" ( 181 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 182 | cd %BUILDDIR%/latex 183 | make all-pdf-ja 184 | cd %~dp0 185 | echo. 186 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 187 | goto end 188 | ) 189 | 190 | if "%1" == "text" ( 191 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 192 | if errorlevel 1 exit /b 1 193 | echo. 194 | echo.Build finished. The text files are in %BUILDDIR%/text. 195 | goto end 196 | ) 197 | 198 | if "%1" == "man" ( 199 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 200 | if errorlevel 1 exit /b 1 201 | echo. 202 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 203 | goto end 204 | ) 205 | 206 | if "%1" == "texinfo" ( 207 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 208 | if errorlevel 1 exit /b 1 209 | echo. 210 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 211 | goto end 212 | ) 213 | 214 | if "%1" == "gettext" ( 215 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 216 | if errorlevel 1 exit /b 1 217 | echo. 218 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 219 | goto end 220 | ) 221 | 222 | if "%1" == "changes" ( 223 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 224 | if errorlevel 1 exit /b 1 225 | echo. 226 | echo.The overview file is in %BUILDDIR%/changes. 227 | goto end 228 | ) 229 | 230 | if "%1" == "linkcheck" ( 231 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 232 | if errorlevel 1 exit /b 1 233 | echo. 234 | echo.Link check complete; look for any errors in the above output ^ 235 | or in %BUILDDIR%/linkcheck/output.txt. 236 | goto end 237 | ) 238 | 239 | if "%1" == "doctest" ( 240 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 241 | if errorlevel 1 exit /b 1 242 | echo. 243 | echo.Testing of doctests in the sources finished, look at the ^ 244 | results in %BUILDDIR%/doctest/output.txt. 245 | goto end 246 | ) 247 | 248 | if "%1" == "coverage" ( 249 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 250 | if errorlevel 1 exit /b 1 251 | echo. 252 | echo.Testing of coverage in the sources finished, look at the ^ 253 | results in %BUILDDIR%/coverage/python.txt. 254 | goto end 255 | ) 256 | 257 | if "%1" == "xml" ( 258 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 259 | if errorlevel 1 exit /b 1 260 | echo. 261 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 262 | goto end 263 | ) 264 | 265 | if "%1" == "pseudoxml" ( 266 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 267 | if errorlevel 1 exit /b 1 268 | echo. 269 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 270 | goto end 271 | ) 272 | 273 | if "%1" == "dummy" ( 274 | %SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy 275 | if errorlevel 1 exit /b 1 276 | echo. 277 | echo.Build finished. Dummy builder generates no files. 278 | goto end 279 | ) 280 | 281 | :end 282 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /docs/testing.rst: -------------------------------------------------------------------------------- 1 | .. _chapter-testing: 2 | 3 | Testing 4 | ======= 5 | 6 | fonzie has an assortment of test cases and code quality checks to catch 7 | potential problems during development. To run them all in the version of Python 8 | you chose for your virtualenv: 9 | 10 | .. code-block:: bash 11 | 12 | $ make validate 13 | 14 | To run just the unit tests: 15 | 16 | .. code-block:: bash 17 | 18 | $ make test 19 | 20 | To run just the unit tests and check diff coverage 21 | 22 | .. code-block:: bash 23 | 24 | $ make diff_cover 25 | 26 | To run just the code quality checks: 27 | 28 | .. code-block:: bash 29 | 30 | $ make lint 31 | 32 | To run the unit tests under every supported Python version and the code 33 | quality checks: 34 | 35 | .. code-block:: bash 36 | 37 | $ make test-all 38 | 39 | To generate and open an HTML report of how much of the code is covered by 40 | test cases: 41 | 42 | .. code-block:: bash 43 | 44 | $ make coverage 45 | -------------------------------------------------------------------------------- /dredd.yml: -------------------------------------------------------------------------------- 1 | dry-run: null 2 | hookfiles: null 3 | # FIXME: define nodejs as the server language to avoid having to install python 4 | # hooks since we don't use them for now. Switching to python language means 5 | # building our own Dredd Docker image for python servers. 6 | language: nodejs 7 | init: false 8 | custom: {} 9 | names: false 10 | only: [] 11 | reporter: [] 12 | output: [] 13 | header: [] 14 | sorted: false 15 | user: null 16 | inline-errors: false 17 | details: false 18 | method: [] 19 | color: true 20 | logLevel: warning 21 | path: [] 22 | hooks-worker-timeout: 5000 23 | hooks-worker-connect-timeout: 1500 24 | hooks-worker-connect-retry: 500 25 | hooks-worker-after-connect-wait: 100 26 | hooks-worker-term-timeout: 5000 27 | hooks-worker-term-retry: 500 28 | hooks-worker-handler-host: 127.0.0.1 29 | hooks-worker-handler-port: 61321 30 | config: ./dredd.yml 31 | blueprint: fonzie-v1-0.apib 32 | endpoint: 'http://lms:8000/api/v1.0' 33 | -------------------------------------------------------------------------------- /edx-platform/config/cms/docker_run_development.py: -------------------------------------------------------------------------------- 1 | from docker_run_production import * 2 | 3 | 4 | DEBUG = True 5 | 6 | REQUIRE_DEBUG = True 7 | 8 | STATICFILES_STORAGE = "openedx.core.storage.DevelopmentStorage" 9 | 10 | PIPELINE_ENABLED = False 11 | 12 | EMAIL_BACKEND = config( 13 | "EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend" 14 | ) 15 | 16 | ALLOWED_HOSTS = ["*"] 17 | 18 | SESSION_ENGINE = "django.contrib.sessions.backends.cached_db" 19 | 20 | MEDIA_ROOT = "/edx/app/edxapp/data" 21 | STATIC_ROOT = "/edx/app/edxapp/data/static/studio" 22 | -------------------------------------------------------------------------------- /edx-platform/config/lms/docker_run_development.py: -------------------------------------------------------------------------------- 1 | from .docker_run_production import * 2 | 3 | 4 | DEBUG = True 5 | 6 | PIPELINE_ENABLED = False 7 | 8 | STATICFILES_STORAGE = "openedx.core.storage.DevelopmentStorage" 9 | 10 | STATIC_ROOT = "/edx/app/edxapp/data/static" 11 | STATIC_URL = "/static/" 12 | MEDIA_ROOT = "/edx/app/edxapp/data/media" 13 | LOG_DIR = "/data/log" 14 | 15 | EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" 16 | SECRET_KEY = "foo" 17 | 18 | ALLOWED_HOSTS = ["*"] 19 | 20 | LOGGING["handlers"].update( 21 | local={"class": "logging.NullHandler"}, 22 | tracking={"class": "logging.NullHandler"}, 23 | ) 24 | 25 | INSTALLED_APPS += ("fonzie",) 26 | 27 | ROOT_URLCONF = "fonzie.urls.lms_root" 28 | 29 | # Disable CourseTalk service (student course reviewing) 30 | COURSE_REVIEWS_TOOL_PROVIDER_FRAGMENT_NAME = None 31 | COURSE_REVIEWS_TOOL_PROVIDER_PLATFORM_KEY = None 32 | 33 | # Django Rest Framework (aka DRF) 34 | REST_FRAMEWORK = { 35 | "ALLOWED_VERSIONS": ("1.0",), 36 | "DEFAULT_VERSION": "1.0", 37 | "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning", 38 | "DEFAULT_AUTHENTICATION_CLASSES": [ 39 | "rest_framework.authentication.SessionAuthentication" 40 | ], 41 | } 42 | 43 | SIMPLE_JWT = { 44 | "ALGORITHM": "HS256", 45 | "SIGNING_KEY": "ThisIsAnExampleKeyForDevPurposeOnly", 46 | "USER_ID_FIELD": "username", 47 | "USER_ID_CLAIM": "username", 48 | } 49 | 50 | FEATURES["ENABLE_DISCUSSION_SERVICE"] = False 51 | 52 | FEATURES["AUTOMATIC_AUTH_FOR_TESTING"] = True 53 | FEATURES["RESTRICT_AUTOMATIC_AUTH"] = False 54 | 55 | FEATURES["ENABLE_GRADE_DOWNLOADS"] = True 56 | FEATURES["ALLOW_COURSE_STAFF_GRADE_DOWNLOADS"] = True 57 | FEATURES["ENABLE_ASYNC_ANSWER_DISTRIBUTION"] = False 58 | FEATURES["ENABLE_INSTRUCTOR_BACKGROUND_TASKS"] = False 59 | 60 | GRADES_DOWNLOAD = { 61 | "STORAGE_CLASS": "django.core.files.storage.FileSystemStorage", 62 | "STORAGE_KWARGS": { 63 | "location": "/data/export", 64 | "base_url": "/api/v1.0/acl/report", 65 | }, 66 | } 67 | -------------------------------------------------------------------------------- /edx-platform/config/lms/docker_run_test.py: -------------------------------------------------------------------------------- 1 | from docker_run_development import * 2 | 3 | 4 | ENVIRONMENT = 'test' 5 | 6 | TEST_ROOT = "/data/test_root" 7 | COMMON_TEST_DATA_ROOT = COMMON_ROOT / "test" / "data" 8 | DATABASES = { 9 | 'default': { 10 | 'ENGINE': 'django.db.backends.sqlite3', 11 | 'NAME': TEST_ROOT + '/db/fun.db' 12 | }, 13 | } 14 | 15 | os.environ["EDXAPP_TEST_MONGO_HOST"] = "mongodb" 16 | os.environ["EDXAPP_TEST_MONGO_PORT"] = "27017" 17 | 18 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 19 | 20 | SECRET_KEY = "foo" 21 | 22 | PIPELINE_ENABLED = False 23 | 24 | STATICFILES_STORAGE = 'openedx.core.storage.DevelopmentStorage' 25 | 26 | FEATURES['ENABLE_DISCUSSION_SERVICE'] = False 27 | 28 | ALLOWED_HOSTS = ["*"] 29 | CORS_ALLOW_INSECURE = True, 30 | CORS_ORIGIN_ALLOW_ALL = True, 31 | 32 | LOGGING['handlers'].update( 33 | local={'class': 'logging.NullHandler'}, 34 | tracking={'class': 'logging.NullHandler'}, 35 | ) 36 | -------------------------------------------------------------------------------- /edx-platform/reports/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openfun/fonzie/52068beec32b357e52401c4ddb2d18895a50e771/edx-platform/reports/.gitkeep -------------------------------------------------------------------------------- /fonzie-v1-0.apib: -------------------------------------------------------------------------------- 1 | FORMAT: 1A 2 | 3 | # fonzie: an Open edX API 4 | 5 | # Group Status 6 | 7 | API status checking. 8 | 9 | ## Version [/status/version] 10 | 11 | ### Retrieve API version (SemVer string) [GET] 12 | 13 | + Request 14 | + Headers 15 | 16 | Accept: application/json 17 | 18 | + Response 200 (application/json) 19 | 20 | { 21 | "version": "0.7.0" 22 | } 23 | -------------------------------------------------------------------------------- /fonzie/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Fonzie, A FUN API for Open edX. 3 | """ 4 | from __future__ import absolute_import, unicode_literals 5 | 6 | from os import path 7 | 8 | import pkg_resources 9 | from setuptools.config import read_configuration 10 | 11 | 12 | def _extract_version(package_name): 13 | """ 14 | Extract fonzie version. 15 | 16 | Get package version from installed distribution or configuration file if not installed 17 | """ 18 | try: 19 | return pkg_resources.get_distribution(package_name).version 20 | except pkg_resources.DistributionNotFound: 21 | _conf = read_configuration( 22 | path.join(path.dirname(path.dirname(__file__)), "setup.cfg") 23 | ) 24 | return _conf["metadata"]["version"] 25 | 26 | 27 | __version__ = _extract_version("fonzie") 28 | 29 | default_app_config = "fonzie.apps.FonzieConfig" # pylint: disable=invalid-name 30 | -------------------------------------------------------------------------------- /fonzie/apps.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | fonzie Django application initialization. 4 | """ 5 | 6 | from __future__ import absolute_import, unicode_literals 7 | 8 | from django.apps import AppConfig 9 | 10 | 11 | class FonzieConfig(AppConfig): 12 | """Configuration for the fonzie Django application.""" 13 | 14 | name = "fonzie" 15 | -------------------------------------------------------------------------------- /fonzie/conf/locale/config.yaml: -------------------------------------------------------------------------------- 1 | # Configuration for i18n workflow. 2 | 3 | locales: 4 | - en # English - Source Language 5 | - am # Amharic 6 | - ar # Arabic 7 | - az # Azerbaijani 8 | - bg_BG # Bulgarian (Bulgaria) 9 | - bn_BD # Bengali (Bangladesh) 10 | - bn_IN # Bengali (India) 11 | - bs # Bosnian 12 | - ca # Catalan 13 | - ca@valencia # Catalan (Valencia) 14 | - cs # Czech 15 | - cy # Welsh 16 | - da # Danish 17 | - de_DE # German (Germany) 18 | - el # Greek 19 | - en # English 20 | - en_GB # English (United Kingdom) 21 | # Don't pull these until we figure out why pages randomly display in these locales, 22 | # when the user's browser is in English and the user is not logged in. 23 | # - en@lolcat # LOLCAT English 24 | # - en@pirate # Pirate English 25 | - es_419 # Spanish (Latin America) 26 | - es_AR # Spanish (Argentina) 27 | - es_EC # Spanish (Ecuador) 28 | - es_ES # Spanish (Spain) 29 | - es_MX # Spanish (Mexico) 30 | - es_PE # Spanish (Peru) 31 | - et_EE # Estonian (Estonia) 32 | - eu_ES # Basque (Spain) 33 | - fa # Persian 34 | - fa_IR # Persian (Iran) 35 | - fi_FI # Finnish (Finland) 36 | - fil # Filipino 37 | - fr # French 38 | - gl # Galician 39 | - gu # Gujarati 40 | - he # Hebrew 41 | - hi # Hindi 42 | - hr # Croatian 43 | - hu # Hungarian 44 | - hy_AM # Armenian (Armenia) 45 | - id # Indonesian 46 | - it_IT # Italian (Italy) 47 | - ja_JP # Japanese (Japan) 48 | - kk_KZ # Kazakh (Kazakhstan) 49 | - km_KH # Khmer (Cambodia) 50 | - kn # Kannada 51 | - ko_KR # Korean (Korea) 52 | - lt_LT # Lithuanian (Lithuania) 53 | - ml # Malayalam 54 | - mn # Mongolian 55 | - mr # Marathi 56 | - ms # Malay 57 | - nb # Norwegian Bokmål 58 | - ne # Nepali 59 | - nl_NL # Dutch (Netherlands) 60 | - or # Oriya 61 | - pl # Polish 62 | - pt_BR # Portuguese (Brazil) 63 | - pt_PT # Portuguese (Portugal) 64 | - ro # Romanian 65 | - ru # Russian 66 | - si # Sinhala 67 | - sk # Slovak 68 | - sl # Slovenian 69 | - sq # Albanian 70 | - sr # Serbian 71 | - ta # Tamil 72 | - te # Telugu 73 | - th # Thai 74 | - tr_TR # Turkish (Turkey) 75 | - uk # Ukranian 76 | - ur # Urdu 77 | - uz # Uzbek 78 | - vi # Vietnamese 79 | - zh_CN # Chinese (China) 80 | - zh_HK # Chinese (Hong Kong) 81 | - zh_TW # Chinese (Taiwan) 82 | 83 | # The locales used for fake-accented English, for testing. 84 | dummy_locales: 85 | - eo 86 | -------------------------------------------------------------------------------- /fonzie/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Database models for fonzie. 4 | """ 5 | 6 | from __future__ import absolute_import, unicode_literals 7 | -------------------------------------------------------------------------------- /fonzie/urls/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | URLs for fonzie. 4 | """ 5 | from __future__ import absolute_import, unicode_literals 6 | 7 | from django.conf.urls import include, url 8 | 9 | from ..apps import FonzieConfig 10 | from . import acl as acl_urls 11 | from . import status as status_urls 12 | from . import user as user_urls 13 | 14 | app_name = FonzieConfig.name 15 | 16 | # For now, as DRF OpenAPI only supports URLPathVersioning, we prefix API root 17 | # URL by the API release number. *This is a temporary solution*. 18 | # See: https://github.com/limdauto/drf_openapi#4-constraints 19 | # 20 | # TODO: switch to AcceptHeaderVersioning 21 | # http://www.django-rest-framework.org/api-guide/versioning/#acceptheaderversioning 22 | API_PREFIX = r"^v(?P[0-9]+\.[0-9]+)" 23 | 24 | urlpatterns = [ 25 | url(r"{}/status/".format(API_PREFIX), include(status_urls, namespace="status")), 26 | url( 27 | r"{}/acl/".format(API_PREFIX), 28 | include(acl_urls, namespace="acl"), 29 | ), 30 | url(r"{}/user/".format(API_PREFIX), include(user_urls, namespace="user")), 31 | ] 32 | -------------------------------------------------------------------------------- /fonzie/urls/acl.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Access control to instructor files API endpoint 4 | """ 5 | from __future__ import absolute_import, unicode_literals 6 | 7 | from django.conf.urls import url 8 | 9 | from ..apps import FonzieConfig 10 | from ..views.acl import ReportView 11 | 12 | app_name = FonzieConfig.name 13 | urlpatterns = [ 14 | url( 15 | r"^report/(?P[a-f0-9]{40})/(?P[\d\w\-\_\.\+\@]+)$", 16 | ReportView.as_view(), 17 | name="report", 18 | ) 19 | ] 20 | -------------------------------------------------------------------------------- /fonzie/urls/lms_root.py: -------------------------------------------------------------------------------- 1 | """ 2 | URLs for fonzie 3 | This file appends Fonzie urls to edX lms urls. 4 | It is intended to be defined in lms settings this way: 5 | 6 | `ROOT_URLCONF = "fonzie.urls.lms_root"` 7 | """ 8 | from __future__ import absolute_import, unicode_literals 9 | 10 | from django.conf.urls import include, url 11 | 12 | from lms.urls import urlpatterns # pylint: disable=import-error 13 | 14 | # Fonzie urls 15 | urlpatterns += [ 16 | url(r"^api/", include("fonzie.urls", namespace="fonzie")), 17 | ] 18 | -------------------------------------------------------------------------------- /fonzie/urls/status.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | API status endpoints 4 | """ 5 | from __future__ import absolute_import, unicode_literals 6 | 7 | from django.conf.urls import url 8 | 9 | from ..apps import FonzieConfig 10 | from ..views.status import VersionView 11 | 12 | app_name = FonzieConfig.name 13 | urlpatterns = [url(r"^version$", VersionView.as_view(), name="version")] 14 | -------------------------------------------------------------------------------- /fonzie/urls/user.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | API user endpoints 4 | """ 5 | from __future__ import absolute_import, unicode_literals 6 | 7 | from django.conf.urls import url 8 | 9 | from ..apps import FonzieConfig 10 | from ..views.user import UserSessionView 11 | 12 | app_name = FonzieConfig.name 13 | urlpatterns = [url(r"^me/?$", UserSessionView.as_view(), name="me")] 14 | -------------------------------------------------------------------------------- /fonzie/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openfun/fonzie/52068beec32b357e52401c4ddb2d18895a50e771/fonzie/views/__init__.py -------------------------------------------------------------------------------- /fonzie/views/acl.py: -------------------------------------------------------------------------------- 1 | """ 2 | Instructor file API endpoint 3 | """ 4 | # pylint: disable=import-error 5 | from __future__ import absolute_import, unicode_literals 6 | 7 | import hashlib 8 | 9 | import six 10 | from rest_framework import status 11 | from rest_framework.permissions import IsAuthenticated 12 | from rest_framework.response import Response 13 | from rest_framework.views import APIView 14 | 15 | from student.models import CourseAccessRole 16 | from student.roles import CourseStaffRole 17 | 18 | 19 | class ReportView(APIView): 20 | """API endpoint to control access to CSV export files""" 21 | 22 | permission_classes = (IsAuthenticated,) 23 | 24 | # pylint: disable=unused-argument 25 | def get(self, request, version, course_sha1, filename): 26 | """ 27 | API endpoint that control access to instructors CSV export files, 28 | then redirects to Nginx. 29 | Users requesting access to a CSV file sould be superuser or 30 | belong to course staff. 31 | """ 32 | 33 | # Retrieve all courses for which user is staff and compare their sha1 to 34 | # the requested one. 35 | if not request.user.is_superuser: 36 | courses = CourseAccessRole.objects.filter( 37 | user=request.user, role=CourseStaffRole.ROLE 38 | ) 39 | for course in courses: 40 | hashed_course_id = hashlib.sha1( # nosec 41 | six.text_type(course.course_id) 42 | ).hexdigest() 43 | if hashed_course_id == course_sha1: 44 | break 45 | else: 46 | # None matches, user does not have rights to request this file 47 | return Response({}, status=status.HTTP_403_FORBIDDEN) 48 | 49 | # Redirect browser to an internal Nginx location 50 | response = Response({}) 51 | response["Content-Disposition"] = "attachment; filename={0}".format(filename) 52 | response["X-Accel-Redirect"] = "/restricted/{course_sha1}/{filename}".format( 53 | course_sha1=course_sha1, filename=filename 54 | ) 55 | return response 56 | -------------------------------------------------------------------------------- /fonzie/views/status.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | API status views 4 | """ 5 | from __future__ import absolute_import, unicode_literals 6 | 7 | from rest_framework.response import Response # pylint: disable=import-error 8 | from rest_framework.views import APIView # pylint: disable=import-error 9 | 10 | from fonzie import __version__ as fonzie_version 11 | 12 | 13 | class VersionView(APIView): 14 | """API endpoint to get the running API version""" 15 | 16 | # pylint: disable=redefined-builtin 17 | def get(self, request, version, format=None): 18 | """Retrieve API version as a SemVer string""" 19 | return Response({"version": fonzie_version}) 20 | -------------------------------------------------------------------------------- /fonzie/views/user.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | API user views 4 | """ 5 | # pylint: disable=import-error 6 | from __future__ import absolute_import, unicode_literals 7 | 8 | from datetime import datetime 9 | 10 | from rest_framework.permissions import IsAuthenticated 11 | from rest_framework.response import Response 12 | from rest_framework.views import APIView 13 | from rest_framework_simplejwt.tokens import AccessToken 14 | 15 | from django.conf import settings 16 | from django.core.exceptions import ObjectDoesNotExist 17 | from django.utils.decorators import method_decorator 18 | 19 | from openedx.core.lib.api.authentication import SessionAuthenticationAllowInactiveUser 20 | 21 | # Since Hawthorn version, the module `cors_csrf` has been moved from `common.djangoapps` 22 | # to `openedx.core.djangoapps`. So according to the version of Open edX, we need to 23 | # import the module from the right place. 24 | try: 25 | # First try to import the module from `openedx.core.djangoapps` 26 | from openedx.core.djangoapps.cors_csrf.decorators import ensure_csrf_cookie_cross_domain 27 | except ImportError: 28 | # Otherwise, we are using an older version of Open edX, so we import the module 29 | # from `common.djangoapps` 30 | from cors_csrf.decorators import ensure_csrf_cookie_cross_domain 31 | 32 | 33 | class UserSessionView(APIView): 34 | """API endpoint to get the authenticated user information.""" 35 | 36 | authentication_classes = [SessionAuthenticationAllowInactiveUser] 37 | permission_classes = [IsAuthenticated] 38 | 39 | # To be able to update user profile through route api/user/v1/accounts/:username 40 | # we need to provide a valid CSRF Token. This is why we need to ensure that 41 | # the CSRF cookie is set for cross domain requests. 42 | @method_decorator(ensure_csrf_cookie_cross_domain) 43 | # pylint: disable=redefined-builtin 44 | def get(self, request, version, format=None): 45 | """ 46 | Retrieve logged in user, then generate a JWT with a claim containing its 47 | username (unique identifier) and its email. The token's expiration can 48 | be changed through the setting `ACCESS_TOKEN_LIFETIME` (default 5 minutes). 49 | """ 50 | user = request.user 51 | issued_at = datetime.utcnow() 52 | try: 53 | language = user.preferences.get(key="pref-lang").value 54 | except ObjectDoesNotExist: 55 | language = settings.LANGUAGE_CODE 56 | token = AccessToken() 57 | token.payload.update( 58 | { 59 | "email": user.email, 60 | "full_name": user.profile.name, 61 | "iat": issued_at, 62 | "language": language, 63 | "username": user.username, 64 | "is_active": user.is_active, 65 | "is_staff": user.is_staff, 66 | "is_superuser": user.is_superuser, 67 | }, 68 | ) 69 | response = Response( 70 | { 71 | "access_token": str(token), 72 | "username": user.username, 73 | "full_name": user.profile.name, 74 | } 75 | ) 76 | if "newsletter_subscribed" in request.COOKIES: 77 | # delete the cookie, as we only need it once, after registration 78 | token.payload["has_subscribed_to_commercial_newsletter"] = ( 79 | request.COOKIES.get("newsletter_subscribed") == "true" 80 | ) 81 | response.delete_cookie("newsletter_subscribed") 82 | response.data["access_token"] = str(token) 83 | 84 | return response 85 | -------------------------------------------------------------------------------- /locale/config.yaml: -------------------------------------------------------------------------------- 1 | # Configuration for i18n workflow. 2 | 3 | locales: 4 | - en # English - Source Language 5 | - ar # Arabic 6 | - az # Azerbaijani 7 | - bg_BG # Bulgarian (Bulgaria) 8 | - bn_BD # Bengali (Bangladesh) 9 | - bn_IN # Bengali (India) 10 | - bs # Bosnian 11 | - ca # Catalan 12 | - ca@valencia # Catalan (Valencia) 13 | - cs # Czech 14 | - cy # Welsh 15 | - da # Danish 16 | - de_DE # German (Germany) 17 | - el # Greek 18 | - en@lolcat # LOLCAT English 19 | - en@pirate # Pirate English 20 | - es_419 # Spanish (Latin America) 21 | - es_AR # Spanish (Argentina) 22 | - es_EC # Spanish (Ecuador) 23 | - es_ES # Spanish (Spain) 24 | - es_MX # Spanish (Mexico) 25 | - es_PE # Spanish (Peru) 26 | - et_EE # Estonian (Estonia) 27 | - eu_ES # Basque (Spain) 28 | - fa # Persian 29 | - fa_IR # Persian (Iran) 30 | - fi_FI # Finnish (Finland) 31 | - fr # French 32 | - gl # Galician 33 | - gu # Gujarati 34 | - he # Hebrew 35 | - hi # Hindi 36 | - hr # Croatian 37 | - hu # Hungarian 38 | - hy_AM # Armenian (Armenia) 39 | - id # Indonesian 40 | - it_IT # Italian (Italy) 41 | - ja_JP # Japanese (Japan) 42 | - kk_KZ # Kazakh (Kazakhstan) 43 | - km_KH # Khmer (Cambodia) 44 | - kn # Kannada 45 | - ko_KR # Korean (Korea) 46 | - lt_LT # Lithuanian (Lithuania) 47 | - ml # Malayalam 48 | - mn # Mongolian 49 | - ms # Malay 50 | - nb # Norwegian Bokmål 51 | - ne # Nepali 52 | - nl_NL # Dutch (Netherlands) 53 | - or # Oriya 54 | - pl # Polish 55 | - pt_BR # Portuguese (Brazil) 56 | - pt_PT # Portuguese (Portugal) 57 | - ro # Romanian 58 | - ru # Russian 59 | - si # Sinhala 60 | - sk # Slovak 61 | - sl # Slovenian 62 | - th # Thai 63 | - tr_TR # Turkish (Turkey) 64 | - uk # Ukranian 65 | - ur # Urdu 66 | - vi # Vietnamese 67 | - zh_CN # Chinese (China) 68 | - zh_TW # Chinese (Taiwan) 69 | 70 | # The locales used for fake-accented English, for testing. 71 | dummy_locales: 72 | - eo 73 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Django administration utility. 4 | """ 5 | 6 | from __future__ import absolute_import, unicode_literals 7 | 8 | import os 9 | import sys 10 | 11 | PWD = os.path.abspath(os.path.dirname(__file__)) 12 | 13 | if __name__ == '__main__': 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'lms.envs.docker_run') 15 | sys.path.append(PWD) 16 | try: 17 | from django.core.management import execute_from_command_line # pylint: disable=wrong-import-position 18 | except ImportError: 19 | # The above import may fail for some other reason. Ensure that the 20 | # issue is really that Django is missing to avoid masking other 21 | # exceptions on Python 2. 22 | try: 23 | import django # pylint: disable=unused-import, wrong-import-position 24 | except ImportError: 25 | raise ImportError( 26 | "Couldn't import Django. Are you sure it's installed and " 27 | "available on your PYTHONPATH environment variable? Did you " 28 | "forget to activate a virtual environment?" 29 | ) 30 | raise 31 | execute_from_command_line(sys.argv) 32 | -------------------------------------------------------------------------------- /openedx.yaml: -------------------------------------------------------------------------------- 1 | # openedx.yaml 2 | 3 | --- 4 | owner: fun/tech 5 | nick: fonzie 6 | tags: 7 | - tools 8 | oeps: 9 | oep-2: True 10 | oep-3: 11 | state: False 12 | reason: TODO - Implement for this application if appropriate 13 | oep-5: 14 | state: False 15 | reason: TODO - Implement for this application if appropriate 16 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | # *************************** 2 | # ** DO NOT EDIT THIS FILE ** 3 | # *************************** 4 | # 5 | # This file was generated by edx-lint: http://github.com/edx.edx-lint 6 | # 7 | # If you want to change this file, you have two choices, depending on whether 8 | # you want to make a local change that applies only to this repo, or whether 9 | # you want to make a central change that applies to all repos using edx-lint. 10 | # 11 | # LOCAL CHANGE: 12 | # 13 | # 1. Edit the local pylintrc_tweaks file to add changes just to this 14 | # repo's file. 15 | # 16 | # 2. Run: 17 | # 18 | # $ edx_lint write pylintrc 19 | # 20 | # 3. This will modify the local file. Submit a pull request to get it 21 | # checked in so that others will benefit. 22 | # 23 | # 24 | # CENTRAL CHANGE: 25 | # 26 | # 1. Edit the pylintrc file in the edx-lint repo at 27 | # https://github.com/edx/edx-lint/blob/master/edx_lint/files/pylintrc 28 | # 29 | # 2. Make a new version of edx_lint, which involves the usual steps of 30 | # incrementing the version number, submitting and reviewing a pull 31 | # request, and updating the edx-lint version reference in this repo. 32 | # 33 | # 3. Install the newer version of edx-lint. 34 | # 35 | # 4. Run: 36 | # 37 | # $ edx_lint write pylintrc 38 | # 39 | # 5. This will modify the local file. Submit a pull request to get it 40 | # checked in so that others will benefit. 41 | # 42 | # 43 | # 44 | # 45 | # 46 | # STAY AWAY FROM THIS FILE! 47 | # 48 | # 49 | # 50 | # 51 | # 52 | # SERIOUSLY. 53 | # 54 | # ------------------------------ 55 | [MASTER] 56 | ignore = migrations 57 | persistent = yes 58 | load-plugins = caniusepython3.pylint_checker,edx_lint.pylint,pylint_django,pylint_celery 59 | 60 | [MESSAGES CONTROL] 61 | disable = 62 | abstract-class-little-used, 63 | abstract-class-not-used, 64 | bad-builtin, 65 | bad-continuation, 66 | duplicate-code, 67 | fixme, 68 | locally-disabled, 69 | locally-enabled, 70 | logging-format-interpolation, 71 | no-init, 72 | no-self-use, 73 | star-args, 74 | too-few-public-methods, 75 | too-few-public-methods, 76 | too-many-ancestors, 77 | too-many-arguments, 78 | too-many-branches, 79 | too-many-instance-attributes, 80 | too-many-lines, 81 | too-many-locals, 82 | too-many-public-methods, 83 | too-many-return-statements, 84 | unused-wildcard-import 85 | 86 | 87 | [REPORTS] 88 | output-format = text 89 | files-output = no 90 | reports = no 91 | evaluation = 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 92 | 93 | [BASIC] 94 | bad-functions = map,filter,apply,input 95 | module-rgx = (([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 96 | const-rgx = (([A-Z_][A-Z0-9_]*)|(__.*__)|log|urlpatterns|app_name)$ 97 | class-rgx = [A-Z_][a-zA-Z0-9]+$ 98 | function-rgx = ([a-z_][a-z0-9_]{2,40}|test_[a-z0-9_]+)$ 99 | method-rgx = ([a-z_][a-z0-9_]{2,40}|setUp|set[Uu]pClass|tearDown|tear[Dd]ownClass|assert[A-Z]\w*|maxDiff|test_[a-z0-9_]+)$ 100 | attr-rgx = [a-z_][a-z0-9_]{2,30}$ 101 | argument-rgx = [a-z_][a-z0-9_]{2,30}$ 102 | variable-rgx = [a-z_][a-z0-9_]{2,30}$ 103 | class-attribute-rgx = ([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 104 | inlinevar-rgx = [A-Za-z_][A-Za-z0-9_]*$ 105 | good-names = f,i,j,k,db,ex,Run,_,__ 106 | bad-names = foo,bar,baz,toto,tutu,tata 107 | no-docstring-rgx = __.*__$|test_.+|setUp$|setUpClass$|tearDown$|tearDownClass$|Meta$ 108 | docstring-min-length = -1 109 | 110 | [FORMAT] 111 | max-line-length = 120 112 | ignore-long-lines = ^\s*(# )??$ 113 | single-line-if-stmt = no 114 | no-space-check = trailing-comma,dict-separator 115 | max-module-lines = 1000 116 | indent-string = ' ' 117 | 118 | [MISCELLANEOUS] 119 | notes = FIXME,XXX,TODO 120 | 121 | [SIMILARITIES] 122 | min-similarity-lines = 4 123 | ignore-comments = yes 124 | ignore-docstrings = yes 125 | ignore-imports = no 126 | 127 | [TYPECHECK] 128 | ignore-mixin-members = yes 129 | ignored-classes = SQLObject 130 | unsafe-load-any-extension = yes 131 | generated-members = 132 | REQUEST, 133 | acl_users, 134 | aq_parent, 135 | objects, 136 | DoesNotExist, 137 | can_read, 138 | can_write, 139 | get_url, 140 | size, 141 | content, 142 | status_code, 143 | create, 144 | build, 145 | fields, 146 | tag, 147 | org, 148 | course, 149 | category, 150 | name, 151 | revision, 152 | _meta, 153 | 154 | [VARIABLES] 155 | init-import = no 156 | dummy-variables-rgx = _|dummy|unused|.*_unused 157 | additional-builtins = 158 | 159 | [CLASSES] 160 | defining-attr-methods = __init__,__new__,setUp 161 | valid-classmethod-first-arg = cls 162 | valid-metaclass-classmethod-first-arg = mcs 163 | 164 | [DESIGN] 165 | max-args = 5 166 | ignored-argument-names = _.* 167 | max-locals = 15 168 | max-returns = 6 169 | max-branches = 12 170 | max-statements = 50 171 | max-parents = 7 172 | max-attributes = 7 173 | min-public-methods = 2 174 | max-public-methods = 20 175 | 176 | [IMPORTS] 177 | deprecated-modules = regsub,TERMIOS,Bastion,rexec 178 | import-graph = 179 | ext-import-graph = 180 | int-import-graph = 181 | 182 | [EXCEPTIONS] 183 | overgeneral-exceptions = Exception 184 | 185 | # 1a67033d4799199101eddf63b8ed0bef3e61bda7 186 | -------------------------------------------------------------------------------- /pylintrc_tweaks: -------------------------------------------------------------------------------- 1 | # pylintrc tweaks for use with edx_lint. 2 | [MASTER] 3 | ignore = migrations 4 | load-plugins = caniusepython3.pylint_checker,edx_lint.pylint,pylint_django,pylint_celery 5 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | -e .[dev,doc,quality,test,ci] 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | . 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | ;; 2 | ;; Fonzie package 3 | ;; 4 | [metadata] 5 | name = fonzie 6 | version = 0.7.0 7 | description = A FUN API for Open edX 8 | long_description = file: README.rst 9 | author = Open FUN (France Universite Numerique) 10 | author_email = fun.dev@fun-mooc.fr 11 | url = https://github.com/openfun/fonzie 12 | license= AGPL 3.0 13 | keywords = Django, Django Rest Framework, API, Open edX 14 | classifiers = 15 | Development Status :: 4 - Beta 16 | Framework :: Django 17 | Framework :: Django :: 1.8 18 | Intended Audience :: Developers 19 | License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+) 20 | Natural Language :: English 21 | Programming Language :: Python :: 2 22 | Programming Language :: Python :: 2.7 23 | 24 | [options] 25 | include_package_data = true 26 | install_requires = 27 | # Add direct dependencies here 28 | djangorestframework-simplejwt 29 | packages = find: 30 | zip_safe = False 31 | 32 | [options.extras_require] 33 | dev = 34 | diff-cover==4.0.0 35 | edx-lint==1.4.1 36 | edx-i18n-tools==0.4.5 37 | twine==1.15.0 38 | wheel 39 | doc = 40 | doc8==0.8.1 41 | edx_sphinx_theme==1.6.1 42 | readme_renderer==28.0 43 | Sphinx==1.8.5 44 | quality = 45 | bandit==1.6.2 46 | caniusepython3==7.3.0 47 | edx-lint==1.4.1 48 | isort==4.3.21 49 | pycodestyle==2.6.0 50 | pydocstyle==3.0.0 51 | pyroma==2.6 52 | test = 53 | factory-boy==2.12.0 54 | nose==1.3.7 55 | pytest==4.6.11 56 | pytest-cov==2.11.1 57 | pytest-django==3.10.0 58 | requests==2.9.1 59 | ci = 60 | codecov==2.1.13 61 | sphinx==1.8.5 62 | twine==1.15.0 63 | 64 | [options.packages.find] 65 | exclude = 66 | node_modules 67 | tests 68 | 69 | [wheel] 70 | universal = 1 71 | 72 | ;; 73 | ;; Third-party packages configuration 74 | ;; 75 | [doc8] 76 | max-line-length = 120 77 | 78 | [isort] 79 | line_length = 120 80 | known_edx = 81 | known_django = django 82 | known_djangoapp = model_utils 83 | known_first_party = fonzie 84 | sections = FUTURE,STDLIB,THIRDPARTY,DJANGO,DJANGOAPP,EDX,FIRSTPARTY,LOCALFOLDER 85 | 86 | [pycodestyle] 87 | exclude = .git,.tox,migrations 88 | max-line-length = 99 89 | 90 | [pydocstyle] 91 | ; D101 = Missing docstring in public class 92 | ; D104 = Missing docstring in public package 93 | ; D107 = Missing docstring in __init__ 94 | ; D200 = One-line docstring should fit on one line with quotes 95 | ; D203 = 1 blank line required before class docstring 96 | ; D212 = Multi-line docstring summary should start at the first line 97 | ignore = D101,D104,D107,D200,D203,D212 98 | match-dir = (?!migrations) 99 | 100 | [tool:isort] 101 | known_third_party = rest_framework 102 | include_trailing_comma = True 103 | line_length = 88 104 | multi_line_output = 3 105 | use_parentheses = True 106 | skip_glob = venv 107 | 108 | [tool:pytest] 109 | addopts = 110 | --reuse-db 111 | --no-migrations 112 | --cov /edx/app/fonzie/fonzie 113 | --cov-report term-missing 114 | --cov-report xml 115 | testpaths = 116 | /edx/app/fonzie/tests 117 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from setuptools import setup 4 | 5 | setup() 6 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openfun/fonzie/52068beec32b357e52401c4ddb2d18895a50e771/tests/__init__.py -------------------------------------------------------------------------------- /tests/views/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openfun/fonzie/52068beec32b357e52401c4ddb2d18895a50e771/tests/views/__init__.py -------------------------------------------------------------------------------- /tests/views/test_acl.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Tests for the `fonzie` acl module. 4 | """ 5 | # pylint: disable=no-member,import-error 6 | from __future__ import absolute_import, unicode_literals 7 | 8 | import hashlib 9 | 10 | import six 11 | from rest_framework import status 12 | from rest_framework.test import APITestCase 13 | 14 | from django.core.urlresolvers import reverse 15 | 16 | from student.roles import CourseStaffRole 17 | from student.tests.factories import UserFactory 18 | from xmodule.modulestore import ModuleStoreEnum 19 | from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase 20 | from xmodule.modulestore.tests.factories import CourseFactory 21 | 22 | 23 | class ReportViewTestCase(ModuleStoreTestCase, APITestCase): 24 | """Tests for the InstructorFilesView API endpoint""" 25 | 26 | def setUp(self): 27 | """ 28 | Set common parameters for unit tests. 29 | """ 30 | super(ReportViewTestCase, self).setUp() 31 | 32 | # this file format is used for student profiles 33 | self.filename = "fun_test_00_student_profile_info_2019-04-19-1406.csv" 34 | self.course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split) 35 | self.course_staff = UserFactory( 36 | username="course_staff", email="course_staff@fun-mooc.fr" 37 | ) 38 | CourseStaffRole(self.course.id).add_users(self.course_staff) 39 | self.course_sha1 = hashlib.sha1(six.text_type(self.course.id)).hexdigest() 40 | 41 | self.url = reverse( 42 | "fonzie:acl:report", 43 | kwargs={ 44 | "version": "1.0", 45 | "course_sha1": self.course_sha1, 46 | "filename": self.filename, 47 | }, 48 | ) 49 | 50 | def test_user_is_not_logged_in(self): 51 | """ 52 | User should be logged in, if not the view should return 403 53 | """ 54 | response = self.client.get(self.url, format="json") 55 | self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) 56 | 57 | def test_user_is_not_staff(self): 58 | """ 59 | User should not be a just student, if so the view should return 403 60 | """ 61 | normal_user = UserFactory( 62 | username="normal_user", email="normal_user@fun-mooc.fr" 63 | ) 64 | self.client.force_authenticate(normal_user) 65 | response = self.client.get(self.url, format="json") 66 | self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) 67 | 68 | def test_user_is_staff_on_other_course(self): 69 | """ 70 | User should belong to this course staff, if not the view should return 403 71 | """ 72 | course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split) 73 | other_course_staff = UserFactory( 74 | username="other_course_staff", email="other_course_staff@fun-mooc.fr" 75 | ) 76 | CourseStaffRole(course.id).add_users(other_course_staff) 77 | self.client.force_authenticate(other_course_staff) 78 | response = self.client.get(self.url, format="json") 79 | self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) 80 | 81 | def test_user_has_correct_rights(self): 82 | """ 83 | User fullfils all conditions, then he should be redirected to Nginx 84 | """ 85 | self.client.force_authenticate(self.course_staff) 86 | response = self.client.get(self.url) 87 | self.assertEqual(response.status_code, status.HTTP_200_OK) 88 | 89 | self.assertEqual( 90 | response.get("X-Accel-Redirect"), 91 | "/restricted/%s/%s" % (self.course_sha1, self.filename), 92 | ) 93 | self.assertEqual( 94 | response.get("Content-Disposition"), 95 | "attachment; filename=%s" % self.filename, 96 | ) 97 | 98 | def test_user_can_download_problem_response(self): 99 | """ 100 | User fullfils all conditions and downloads a problem response file, 101 | he should be redirected to Nginx 102 | """ 103 | # this file format is used by problem responses files 104 | filename = ( 105 | "edX_DemoX_Demo_Course_student_state_from_block-v1_edX+DemoX" 106 | "+Demo_Course+type@problem+block@d1b84dcd39b0423d9e288f27f0f7f242_" 107 | "2019-10-09-1219.csv" 108 | ) 109 | url = reverse( 110 | "fonzie:acl:report", 111 | kwargs={ 112 | "version": "1.0", 113 | "course_sha1": self.course_sha1, 114 | "filename": filename, 115 | }, 116 | ) 117 | 118 | self.client.force_authenticate(self.course_staff) 119 | response = self.client.get(url) 120 | self.assertEqual(response.status_code, status.HTTP_200_OK) 121 | 122 | self.assertEqual( 123 | response.get("X-Accel-Redirect"), 124 | "/restricted/%s/%s" % (self.course_sha1, filename), 125 | ) 126 | self.assertEqual( 127 | response.get("Content-Disposition"), 128 | "attachment; filename=%s" % filename, 129 | ) 130 | 131 | def test_user_has_correct_rights_old_course_id_format(self): 132 | """ 133 | Ensure endpoint also works with legacy course id format 134 | (org.1/course_1/Run_1 against course-v1:org.1+course_1+Run_1) 135 | """ 136 | course = CourseFactory.create() # create a course with old course_id format 137 | CourseStaffRole(course.id).add_users(self.course_staff) 138 | course_id = six.text_type(course.id) 139 | self.client.force_authenticate(self.course_staff) 140 | url = reverse( 141 | "fonzie:acl:report", 142 | kwargs={ 143 | "version": "1.0", 144 | "course_sha1": hashlib.sha1(course_id).hexdigest(), 145 | "filename": self.filename, 146 | }, 147 | ) 148 | response = self.client.get(url) 149 | self.assertEqual(response.status_code, status.HTTP_200_OK) 150 | 151 | self.assertEqual( 152 | response.get("X-Accel-Redirect"), 153 | "/restricted/%s/%s" 154 | % (hashlib.sha1(six.text_type(course_id)).hexdigest(), self.filename), 155 | ) 156 | 157 | def test_user_is_superuser(self): 158 | """ 159 | Super users should always get requested file. 160 | """ 161 | super_user = UserFactory( 162 | username="super_user", 163 | email="super_user@fun-mooc.fr", 164 | is_superuser=True 165 | ) 166 | self.client.force_authenticate(super_user) 167 | response = self.client.get(self.url, format="json") 168 | self.assertEqual(response.status_code, status.HTTP_200_OK) 169 | 170 | self.assertEqual( 171 | response.get("X-Accel-Redirect"), 172 | "/restricted/%s/%s" % (self.course_sha1, self.filename), 173 | ) 174 | self.assertEqual( 175 | response.get("Content-Disposition"), 176 | "attachment; filename=%s" % self.filename, 177 | ) 178 | -------------------------------------------------------------------------------- /tests/views/test_status.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Tests for the `fonzie` views module. 4 | """ 5 | 6 | from __future__ import absolute_import, unicode_literals 7 | 8 | from rest_framework import status # pylint: disable=import-error 9 | from rest_framework.test import APITestCase # pylint: disable=import-error 10 | 11 | from django.core.urlresolvers import reverse 12 | 13 | from fonzie import __version__ as fonzie_version 14 | 15 | 16 | class VersionViewTestCase(APITestCase): 17 | """Tests for the Versionview""" 18 | 19 | def setUp(self): 20 | """Set view url""" 21 | super(VersionViewTestCase, self).setUp() 22 | 23 | self.url = reverse("fonzie:status:version", kwargs={"version": "1.0"}) 24 | 25 | def test_get(self): 26 | """HTTP/GET returns API version""" 27 | 28 | response = self.client.get(self.url, format="json") 29 | 30 | self.assertEqual(response.status_code, status.HTTP_200_OK) 31 | self.assertJSONEqual(response.content, {"version": fonzie_version}) 32 | 33 | def test_post(self): 34 | """HTTP/POST should return a 405""" 35 | 36 | response = self.client.post(self.url, data={}, format="json") 37 | 38 | self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED) 39 | -------------------------------------------------------------------------------- /tests/views/test_user.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | """ 3 | Tests for the `fonzie` user module. 4 | """ 5 | 6 | # pylint: disable=no-member,import-error 7 | from __future__ import absolute_import, unicode_literals 8 | 9 | import random 10 | 11 | import jwt 12 | from rest_framework import status 13 | from rest_framework.test import APITestCase 14 | 15 | from django.core.urlresolvers import reverse 16 | from django.test import override_settings 17 | 18 | from openedx.core.djangoapps.user_api.tests.factories import UserPreferenceFactory 19 | from student.tests.factories import UserFactory, UserProfileFactory 20 | 21 | 22 | class UserViewTestCase(APITestCase): 23 | """Tests for the User API endpoint""" 24 | 25 | def setUp(self): 26 | """ 27 | Set common parameters for the test suite. 28 | """ 29 | super(UserViewTestCase, self).setUp() 30 | 31 | self.url = reverse("fonzie:user:me", kwargs={"version": "1.0"}) 32 | 33 | def test_user_me_with_anonymous_user(self): 34 | """ 35 | If user is not authenticated, view should return a 403 status 36 | """ 37 | response = self.client.get(self.url) 38 | self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) 39 | 40 | @override_settings( 41 | LANGUAGE_CODE="de", 42 | FEATURES={'ENABLE_CROSS_DOMAIN_CSRF_COOKIE': True}, 43 | CROSS_DOMAIN_CSRF_COOKIE_NAME="edx_csrf_token", 44 | CROSS_DOMAIN_CSRF_COOKIE_DOMAIN="localhost", 45 | ) 46 | def test_user_me_with_logged_in_user(self): 47 | """ 48 | If user is authenticated through Django session, view should return 49 | a JSON object containing the username and a JWT access token and a cross domain 50 | csrf token cookie should be set. 51 | """ 52 | user = UserFactory.create( 53 | username="fonzie", 54 | email="arthur_fonzarelli@fun-mooc.fr", 55 | is_active=random.choice([True, False]), 56 | is_staff=random.choice([True, False]), 57 | is_superuser=random.choice([True, False]), 58 | ) 59 | UserProfileFactory.build(user=user, name="Arthur Fonzarelli") 60 | self.client.force_authenticate(user=user) 61 | 62 | response = self.client.get(self.url) 63 | token = jwt.decode( 64 | response.data["access_token"], 65 | "ThisIsAnExampleKeyForDevPurposeOnly", 66 | options={ 67 | "require": [ 68 | "email", 69 | "exp", 70 | "iat", 71 | "jti", 72 | "token_type", 73 | "username", 74 | "language", 75 | "is_active", 76 | "is_staff", 77 | "is_superuser", 78 | ] 79 | }, 80 | ) 81 | 82 | self.assertEqual(response.status_code, status.HTTP_200_OK) 83 | self.assertEqual(response.csrf_cookie_set, True) 84 | self.assertEqual(response.cookies.get('edx_csrf_token').key, 'edx_csrf_token') 85 | self.assertEqual(response.cookies.get('edx_csrf_token').get('domain'), 'localhost') 86 | self.assertEqual(response.data["username"], "fonzie") 87 | self.assertEqual(token["username"], "fonzie") 88 | self.assertEqual(token["full_name"], "Arthur Fonzarelli") 89 | self.assertEqual(token["email"], "arthur_fonzarelli@fun-mooc.fr") 90 | self.assertEqual(token["is_active"], user.is_active) 91 | self.assertEqual(token["is_staff"], user.is_staff) 92 | self.assertEqual(token["is_superuser"], user.is_superuser) 93 | # When the user has no language preference, LANGUAGE_CODE should be used 94 | self.assertEqual(token["language"], 'de') 95 | self.assertEqual(token["token_type"], "access") 96 | self.assertIsInstance(token["exp"], int) 97 | self.assertIsInstance(token["iat"], int) 98 | 99 | def test_user_me_with_logged_in_user_and_language_preference(self): 100 | """ 101 | If user is authenticated through Django session, and it sets a language 102 | in its preferences, the JWT access token returned by the view should contain 103 | the language. 104 | """ 105 | user = UserFactory.create( 106 | username="joanie", 107 | email="joanie_cunningham@fun-mooc.fr", 108 | ) 109 | UserPreferenceFactory(user=user, key="pref-lang", value="fr") 110 | self.client.force_authenticate(user=user) 111 | 112 | response = self.client.get(self.url) 113 | token = jwt.decode( 114 | response.data["access_token"], 115 | "ThisIsAnExampleKeyForDevPurposeOnly", 116 | options={ 117 | "require": [ 118 | "email", 119 | "exp", 120 | "iat", 121 | "jti", 122 | "token_type", 123 | "username", 124 | "language", 125 | "is_active", 126 | "is_staff", 127 | "is_superuser", 128 | ] 129 | }, 130 | ) 131 | 132 | self.assertEqual(response.status_code, status.HTTP_200_OK) 133 | self.assertEqual(response.data["username"], "joanie") 134 | self.assertEqual(token["email"], "joanie_cunningham@fun-mooc.fr") 135 | self.assertEqual(token["username"], "joanie") 136 | self.assertEqual(token["language"], "fr") 137 | -------------------------------------------------------------------------------- /tests/views/test_versioning.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Tests for the `fonzie` views module. 4 | """ 5 | 6 | from __future__ import absolute_import, unicode_literals 7 | 8 | from rest_framework import status # pylint: disable=import-error 9 | from rest_framework.test import APITestCase # pylint: disable=import-error 10 | 11 | from django.core.urlresolvers import reverse 12 | 13 | from fonzie import __version__ as fonzie_version 14 | 15 | 16 | class VersioningSchemaTestCase(APITestCase): 17 | """Tests for the API versioning schema""" 18 | 19 | def setUp(self): 20 | """Set view url""" 21 | super(VersioningSchemaTestCase, self).setUp() 22 | 23 | self.url = reverse("fonzie:status:version", kwargs={"version": "1.0"}) 24 | 25 | def test_url_path_versioning(self): 26 | """Test allowed API versions in URL Path API versioning""" 27 | 28 | # Default API version: 1.0 29 | url = reverse("fonzie:status:version", kwargs={"version": "1.0"}) 30 | self.assertIn("v1.0", url) 31 | response = self.client.get(url, format="json") 32 | self.assertEqual(response.status_code, status.HTTP_200_OK) 33 | self.assertJSONEqual(response.content, {"version": fonzie_version}) 34 | 35 | # Unimplemented or forbidden API version 36 | url = reverse("fonzie:status:version", kwargs={"version": "1.1"}) 37 | self.assertIn("v1.1", url) 38 | response = self.client.get(url, format="json") 39 | self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) 40 | self.assertJSONEqual( 41 | response.content, {"detail": "Invalid version in URL path."} 42 | ) 43 | --------------------------------------------------------------------------------