├── requirements.txt ├── {{cookiecutter.repo_name}} ├── requirements │ ├── optional.txt │ ├── validation.in │ ├── production.in │ ├── pip-tools.in │ ├── pip-tools.txt │ ├── test.in │ ├── base.in │ ├── doc.in │ ├── monitoring │ │ └── requirements.txt │ ├── quality.in │ ├── dev.in │ └── private.readme ├── {{cookiecutter.repo_name}} │ ├── __init__.py │ ├── static │ │ └── .keep │ ├── apps │ │ ├── __init__.py │ │ ├── api │ │ │ ├── __init__.py │ │ │ ├── v1 │ │ │ │ ├── __init__.py │ │ │ │ ├── views.py │ │ │ │ ├── urls.py │ │ │ │ └── tests │ │ │ │ │ └── __init__.py │ │ │ ├── tests │ │ │ │ └── __init__.py │ │ │ ├── models.py │ │ │ ├── serializers.py │ │ │ └── urls.py │ │ └── core │ │ │ ├── __init__.py │ │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── test_context_processors.py │ │ │ ├── test_models.py │ │ │ └── test_views.py │ │ │ ├── migrations │ │ │ └── __init__.py │ │ │ ├── constants.py │ │ │ ├── context_processors.py │ │ │ ├── admin.py │ │ │ ├── models.py │ │ │ └── views.py │ ├── templates │ │ └── .keep │ ├── settings │ │ ├── __init__.py │ │ ├── private.py.example │ │ ├── test.py │ │ ├── devstack.py │ │ ├── production.py │ │ ├── local.py │ │ ├── utils.py │ │ └── base.py │ ├── wsgi.py │ ├── urls.py │ ├── docker_gunicorn_configuration.py │ └── conf │ │ └── locale │ │ └── config.yaml ├── docs │ ├── __init__.py │ ├── testing.rst │ ├── _static │ │ └── theme_overrides.css │ ├── index.rst │ ├── features.rst │ ├── internationalization.rst │ ├── getting_started.rst │ ├── Makefile │ └── conf.py ├── .bowerrc ├── requirements.txt ├── codecov.yml ├── pylintrc_tweaks ├── CONTRIBUTING.md ├── .coveragerc ├── setup.cfg ├── manage.py ├── .travis.yml ├── .tx │ └── config ├── pytest.ini ├── db_keyword_overrides.yml ├── openedx.yaml ├── docker-compose.yml ├── .gitignore ├── .annotation_safe_list.yml ├── .pii_annotations.yml ├── .github │ └── PULL_REQUEST_TEMPLATE.md ├── README.rst ├── .editorconfig ├── Dockerfile ├── Makefile └── LICENSE.txt ├── .gitignore ├── hooks └── post_gen_project.py ├── .travis.yml ├── cookiecutter.json ├── openedx.yaml ├── Makefile ├── .editorconfig ├── README.rst └── LICENSE.txt /requirements.txt: -------------------------------------------------------------------------------- 1 | cookiecutter 2 | edx-lint 3 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/requirements/optional.txt: -------------------------------------------------------------------------------- 1 | newrelic 2 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/static/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/templates/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/api/v1/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/settings/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/core/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/core/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.pot 3 | *.pyc 4 | repo_name 5 | .idea/ 6 | .python-version 7 | .venv 8 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/api/v1/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/docs/__init__.py: -------------------------------------------------------------------------------- 1 | # Included so Django's startproject command runs against the docs directory 2 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "{{cookiecutter.repo_name}}/static/bower_components", 3 | "interactive": false 4 | } 5 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/api/v1/urls.py: -------------------------------------------------------------------------------- 1 | """ API v1 URLs. """ 2 | 3 | app_name = 'v1' 4 | urlpatterns = [] 5 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/requirements/validation.in: -------------------------------------------------------------------------------- 1 | # Requirements for validation (testing, code quality). 2 | 3 | -r quality.txt 4 | -r test.txt 5 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/api/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Create your tests in sub-packages prefixed with "test_" (e.g. test_models). 2 | -------------------------------------------------------------------------------- /hooks/post_gen_project.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from edx_lint.cmd.write import write_main 5 | 6 | write_main(['pylintrc']) 7 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/api/v1/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Create your tests in sub-packages prefixed with "test_" (e.g. test_views). 2 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/requirements/production.in: -------------------------------------------------------------------------------- 1 | # Packages required in a production environment 2 | -r base.txt 3 | 4 | gevent 5 | gunicorn 6 | mysqlclient 7 | python-memcached 8 | PyYAML>=5.1 9 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/requirements.txt: -------------------------------------------------------------------------------- 1 | # This file is here because many Platforms as a Service look for 2 | # requirements.txt in the root directory of a project. 3 | -r requirements/production.txt 4 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/codecov.yml: -------------------------------------------------------------------------------- 1 | comment: off 2 | coverage: 3 | status: 4 | patch: 5 | default: 6 | target: 100 7 | project: 8 | default: 9 | target: 100 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 3.6 4 | sudo: false 5 | 6 | cache: pip 7 | install: 8 | - pip install -U pip wheel 9 | - make requirements 10 | script: 11 | - make test 12 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/core/constants.py: -------------------------------------------------------------------------------- 1 | """ Constants for the core app. """ 2 | 3 | 4 | class Status: 5 | """Health statuses.""" 6 | OK = u"OK" 7 | UNAVAILABLE = u"UNAVAILABLE" 8 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/requirements/pip-tools.in: -------------------------------------------------------------------------------- 1 | # Just the dependencies to run pip-tools, mainly for the "upgrade" make target 2 | 3 | pip-tools<5.0.0 # Contains pip-compile, used to generate pip requirements files 4 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/pylintrc_tweaks: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | ignore+= ,migrations, settings, wsgi.py 3 | 4 | [BASIC] 5 | const-rgx = (([A-Z_][A-Z0-9_]*)|(__.*__)|log|urlpatterns|logger|User)$ 6 | 7 | [MESSAGES CONTROL] 8 | DISABLE+= ,invalid-name 9 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | This is an Open edX repo, and we welcome your contributions! 4 | Please read the [contributing guidelines](http://edx.readthedocs.org/projects/edx-developer-guide/en/latest/process/index.html). 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/requirements/pip-tools.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # make upgrade 6 | # 7 | click==7.0 # via pip-tools 8 | pip-tools==4.4.1 9 | six==1.12.0 # via pip-tools 10 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/core/context_processors.py: -------------------------------------------------------------------------------- 1 | """ Core context processors. """ 2 | from django.conf import settings 3 | 4 | 5 | def core(_request): 6 | """ Site-wide context processor. """ 7 | return { 8 | 'platform_name': settings.PLATFORM_NAME 9 | } 10 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | data_file = .coverage 4 | source={{cookiecutter.repo_name}} 5 | omit = 6 | {{cookiecutter.repo_name}}/settings* 7 | {{cookiecutter.repo_name}}/conf* 8 | *wsgi.py 9 | *migrations* 10 | *admin.py 11 | *static* 12 | *templates* 13 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/api/models.py: -------------------------------------------------------------------------------- 1 | # Models that can be shared across multiple versions of the API 2 | # should be created here. As the API evolves, models may become more 3 | # specific to a particular version of the API. In this case, the models 4 | # in question should be moved to versioned sub-package. 5 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/requirements/test.in: -------------------------------------------------------------------------------- 1 | # Requirements for test runs. 2 | 3 | -r base.txt # Core dependencies for this package 4 | 5 | code-annotations 6 | coverage 7 | django-dynamic-fixture # library to create dynamic model instances for testing purposes 8 | edx-lint 9 | mock 10 | pytest-cov 11 | pytest-django 12 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/docs/testing.rst: -------------------------------------------------------------------------------- 1 | Testing 2 | ======= 3 | 4 | The command below runs the Python tests and code quality validation—Pylint and Pycodestyle. 5 | 6 | .. code-block:: bash 7 | 8 | $ make validate 9 | 10 | Code quality validation can be run independently with: 11 | 12 | .. code-block:: bash 13 | 14 | $ make quality 15 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/api/serializers.py: -------------------------------------------------------------------------------- 1 | # Serializers that can be shared across multiple versions of the API 2 | # should be created here. As the API evolves, serializers may become more 3 | # specific to a particular version of the API. In this case, the serializers 4 | # in question should be moved to versioned sub-package. 5 | -------------------------------------------------------------------------------- /cookiecutter.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_name": "Full name of the project, e.g. Credentials service or Discovery service", 3 | "repo_name": "repo_name", 4 | "author_name": "edX", 5 | "owner_name": "insert_github_name_of_human_who_owns_repo", 6 | "team_supporting_repo": "insert_edx_github_team_name", 7 | "description": "A short description of the project.", 8 | "port": 8000 9 | } 10 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/setup.cfg: -------------------------------------------------------------------------------- 1 | [pycodestyle] 2 | ignore=E501 3 | max-line-length = 120 4 | exclude=.git,settings,migrations,{{cookiecutter.repo_name}}/static,bower_components,{{cookiecutter.repo_name}}/wsgi.py 5 | 6 | [tool:isort] 7 | indent=' ' 8 | line_length=80 9 | multi_line_output=3 10 | lines_after_imports=2 11 | include_trailing_comma=True 12 | skip= 13 | settings 14 | migrations 15 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/settings/private.py.example: -------------------------------------------------------------------------------- 1 | SOCIAL_AUTH_EDX_OAUTH2_KEY = 'replace-me' 2 | SOCIAL_AUTH_EDX_OAUTH2_SECRET = 'replace-me' 3 | SOCIAL_AUTH_EDX_OAUTH2_URL_ROOT = 'http://127.0.0.1:8000/oauth' 4 | BACKEND_SERVICE_EDX_OAUTH2_KEY = '{{cookiecutter.repo_name}}-backend-service-key' 5 | BACKEND_SERVICE_EDX_OAUTH2_SECRET = '{{cookiecutter.repo_name}}-backend-service-secret' 6 | -------------------------------------------------------------------------------- /openedx.yaml: -------------------------------------------------------------------------------- 1 | # This file describes this Open edX repo, as described in OEP-2: 2 | # http://open-edx-proposals.readthedocs.io/en/latest/oeps/oep-0002.html#specification 3 | 4 | nick: ccdida 5 | oeps: 6 | oep-2: true # Repository metadata 7 | oep-7: true # Python 3 8 | oep-30: # PII annotation 9 | applicable: false 10 | owner: jmbowman 11 | supporting_teams: 12 | - testeng 13 | archived: true -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Django administration utility. 5 | """ 6 | 7 | import os 8 | import sys 9 | 10 | if __name__ == "__main__": 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{cookiecutter.repo_name}}.settings.local") 12 | 13 | from django.core.management import execute_from_command_line 14 | 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/requirements/base.in: -------------------------------------------------------------------------------- 1 | # Core requirements for using this application 2 | 3 | Django>=1.11.23,<2.0 # Web application framework 4 | django-cors-headers 5 | django-extensions 6 | django-rest-swagger 7 | django-waffle 8 | djangorestframework 9 | edx-auth-backends 10 | edx-django-utils 11 | edx-django-release-util 12 | edx-drf-extensions 13 | edx-rest-api-client==1.9.2 14 | pytz 15 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/requirements/doc.in: -------------------------------------------------------------------------------- 1 | # Requirements for documentation validation 2 | 3 | -r test.txt # Core and testing dependencies for this package 4 | 5 | doc8 # reStructuredText style checker 6 | edx_sphinx_theme # edX theme for Sphinx output 7 | readme_renderer # Validates README.rst for usage on PyPI 8 | Sphinx # Documentation builder 9 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/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 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/requirements/monitoring/requirements.txt: -------------------------------------------------------------------------------- 1 | # This requirements.txt file is meant to pull in other requirements.txt files so that 2 | # dependency monitoring tools like gemnasium.com can easily process them. 3 | 4 | # It can not be used to do the full installation because it is not in the correct 5 | # order. 6 | 7 | -r ../dev.txt # Includes validation requirements 8 | -r ../optional.txt 9 | -r ../production.txt 10 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/api/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | Root API URLs. 3 | 4 | All API URLs should be versioned, so urlpatterns should only 5 | contain namespaces for the active versions of the API. 6 | """ 7 | from django.conf.urls import include, url 8 | 9 | from {{cookiecutter.repo_name}}.apps.api.v1 import urls as v1_urls 10 | 11 | 12 | app_name = 'api' 13 | urlpatterns = [ 14 | url(r'^v1/', include(v1_urls)), 15 | ] 16 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/settings/test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from {{cookiecutter.repo_name}}.settings.base import * 4 | 5 | 6 | # IN-MEMORY TEST DATABASE 7 | DATABASES = { 8 | 'default': { 9 | 'ENGINE': 'django.db.backends.sqlite3', 10 | 'NAME': ':memory:', 11 | 'USER': '', 12 | 'PASSWORD': '', 13 | 'HOST': '', 14 | 'PORT': '', 15 | }, 16 | } 17 | # END IN-MEMORY TEST DATABASE 18 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/requirements/quality.in: -------------------------------------------------------------------------------- 1 | # Requirements for code quality checks 2 | 3 | -r base.txt # Core dependencies for this package 4 | 5 | caniusepython3 # Additional Python 3 compatibility pylint checks 6 | edx-lint # edX pylint rules and plugins 7 | isort # to standardize order of imports 8 | pycodestyle # PEP 8 compliance validation 9 | pydocstyle # PEP 257 compliance validation 10 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/requirements/dev.in: -------------------------------------------------------------------------------- 1 | # Additional requirements for development of this application 2 | 3 | -r pip-tools.txt # pip-tools and its deps, for managing requirements files 4 | -r validation.txt # Core, testing, and quality check dependencies 5 | 6 | diff-cover # Changeset diff test coverage 7 | edx-i18n-tools # For i18n_tool dummy 8 | django-debug-toolbar # For debugging Django 9 | #transifex-client # For managing translations 10 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 3.6 4 | sudo: false 5 | 6 | cache: pip 7 | before_install: 8 | - "export DISPLAY=:99.0" 9 | - "sh -e /etc/init.d/xvfb start" 10 | install: 11 | - make requirements 12 | - pip install -U pip wheel codecov 13 | script: 14 | - make validate_translations 15 | - make validate 16 | after_success: 17 | - codecov 18 | deploy: 19 | provider: script 20 | script: make travis_docker_push 21 | on: 22 | branch: master 23 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/docs/index.rst: -------------------------------------------------------------------------------- 1 | .. {{cookiecutter.repo_name}} documentation master file, created by 2 | sphinx-quickstart on Sun Feb 17 11:46:20 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | {{ cookiecutter.project_name }} 7 | ======================================================================= 8 | {{ cookiecutter.description }} 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | 13 | getting_started 14 | testing 15 | features 16 | internationalization 17 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | 4 | [edx-platform.{{cookiecutter.repo_name}}] 5 | file_filter = {{cookiecutter.repo_name}}/conf/locale//LC_MESSAGES/django.po 6 | source_file = {{cookiecutter.repo_name}}/conf/locale/en/LC_MESSAGES/django.po 7 | source_lang = en 8 | type = PO 9 | 10 | [edx-platform.{{cookiecutter.repo_name}}-js] 11 | file_filter = {{cookiecutter.repo_name}}/conf/locale//LC_MESSAGES/djangojs.po 12 | source_file = {{cookiecutter.repo_name}}/conf/locale/en/LC_MESSAGES/djangojs.po 13 | source_lang = en 14 | type = PO 15 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | DJANGO_SETTINGS_MODULE = {{cookiecutter.repo_name}}.settings.test 3 | addopts = --cov {{cookiecutter.repo_name}} --cov-report term-missing --cov-report xml 4 | norecursedirs = .* docs requirements 5 | 6 | # Filter depr warnings coming from packages that we can't control. 7 | filterwarnings = 8 | ignore:.*urlresolvers is deprecated in favor of.*:DeprecationWarning:auth_backends.views:5 9 | ignore:.*invalid escape sequence.*:DeprecationWarning:.*(newrelic|uritemplate|psutil).* 10 | ignore:.*the imp module is deprecated in favour of importlib.*:DeprecationWarning:.*distutils.* 11 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/core/tests/test_context_processors.py: -------------------------------------------------------------------------------- 1 | """ Context processor tests. """ 2 | 3 | from django.test import RequestFactory, TestCase, override_settings 4 | 5 | from {{cookiecutter.repo_name}}.apps.core.context_processors import core 6 | 7 | 8 | PLATFORM_NAME = 'Test Platform' 9 | 10 | 11 | class CoreContextProcessorTests(TestCase): 12 | """ Tests for core.context_processors.core """ 13 | 14 | @override_settings(PLATFORM_NAME=PLATFORM_NAME) 15 | def test_core(self): 16 | request = RequestFactory().get('/') 17 | self.assertDictEqual(core(request), {'platform_name': PLATFORM_NAME}) 18 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/db_keyword_overrides.yml: -------------------------------------------------------------------------------- 1 | # This file is used by the 'check_reserved_keywords' management command to allow specific field names to be overridden 2 | # when checking for conflicts with lists of restricted keywords used in various database/data warehouse tools. 3 | # For more information, see: 4 | # https://openedx.atlassian.net/wiki/spaces/DE/pages/1411809288/Reserved+Keyword+Linter 5 | # and 6 | # https://github.com/edx/edx-django-release-util/release_util/management/commands/check_reserved_keywords.py 7 | # 8 | # overrides should be added in the following format: 9 | # - ModelName.field_name 10 | --- 11 | MYSQL: 12 | SNOWFLAKE: 13 | STITCH: 14 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/requirements/private.readme: -------------------------------------------------------------------------------- 1 | # If there are any Python packages you want to keep in your virtualenv beyond 2 | # those listed in the official requirements files, create a "private.in" file 3 | # and list them there. Generate the corresponding "private.txt" file pinning 4 | # all of their indirect dependencies to specific versions as follows: 5 | 6 | # pip-compile private.in 7 | 8 | # This allows you to use "pip-sync" without removing these packages: 9 | 10 | # pip-sync requirements/*.txt 11 | 12 | # "private.in" and "private.txt" aren't checked into git to avoid merge 13 | # conflicts, and the presence of this file allows "private.*" to be 14 | # included in scripted pip-sync usage without requiring that those files be 15 | # created first. 16 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/openedx.yaml: -------------------------------------------------------------------------------- 1 | # This file describes this Open edX repo, as described in OEP-2: 2 | # http://open-edx-proposals.readthedocs.io/en/latest/oeps/oep-0002.html#specification 3 | 4 | nick: {{cookiecutter.repo_name[:5]}} 5 | oeps: 6 | oep-2: true # Repository metadata 7 | oep-7: true # Python 3 8 | oep-30: true # PII annotation 9 | oep-0018: true # Dependency management 10 | openedx-release: 11 | # The openedx-release key is described in OEP-10: 12 | # https://open-edx-proposals.readthedocs.io/en/latest/oep-0010-proc-openedx-releases.html 13 | # The FAQ might also be helpful: https://openedx.atlassian.net/wiki/spaces/COMM/pages/1331268879/Open+edX+Release+FAQ 14 | maybe: true # Delete this "maybe" line when you have decided about Open edX inclusion. 15 | ref: master 16 | owner: {{cookiecutter.owner_name}} 17 | supporting_teams: 18 | - {{cookiecutter.team_supporting_repo}} 19 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/core/admin.py: -------------------------------------------------------------------------------- 1 | """ Admin configuration for core models. """ 2 | 3 | from django.contrib import admin 4 | from django.contrib.auth.admin import UserAdmin 5 | from django.utils.translation import ugettext_lazy as _ 6 | 7 | from {{cookiecutter.repo_name}}.apps.core.models import User 8 | 9 | 10 | class CustomUserAdmin(UserAdmin): 11 | """ Admin configuration for the custom User model. """ 12 | list_display = ('username', 'email', 'full_name', 'first_name', 'last_name', 'is_staff') 13 | fieldsets = ( 14 | (None, {'fields': ('username', 'password')}), 15 | (_('Personal info'), {'fields': ('full_name', 'first_name', 'last_name', 'email')}), 16 | (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', 17 | 'groups', 'user_permissions')}), 18 | (_('Important dates'), {'fields': ('last_login', 'date_joined')}), 19 | ) 20 | 21 | 22 | admin.site.register(User, CustomUserAdmin) 23 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for {{cookiecutter.repo_name}}. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ 8 | """ 9 | import os 10 | from os.path import abspath, dirname 11 | from sys import path 12 | 13 | from django.conf import settings 14 | from django.contrib.staticfiles.handlers import StaticFilesHandler 15 | from django.core.wsgi import get_wsgi_application 16 | 17 | 18 | SITE_ROOT = dirname(dirname(abspath(__file__))) 19 | path.append(SITE_ROOT) 20 | 21 | application = get_wsgi_application() # pylint: disable=invalid-name 22 | 23 | # Allows the gunicorn app to serve static files in development environment. 24 | # Without this, css in django admin will not be served locally. 25 | if settings.DEBUG: 26 | application = StaticFilesHandler(get_wsgi_application()) 27 | else: 28 | application = get_wsgi_application() 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := test 2 | 3 | .PHONY: requirements test clean 4 | 5 | requirements: 6 | pip install -r requirements.txt 7 | 8 | test: clean 9 | # Create a new project with the default values 10 | cookiecutter . --no-input 11 | 12 | virtualenv -p python3.6 repo_name/.venv 13 | 14 | # Generate requirement pins, install them, and execute the project's tests 15 | . repo_name/.venv/bin/activate && cd repo_name && pip install -U pip==19.3.1 wheel && make upgrade validation_requirements 16 | . repo_name/.venv/bin/activate && cd repo_name && python manage.py makemigrations 17 | . repo_name/.venv/bin/activate && cd repo_name && make migrate validate 18 | 19 | # Ensure translations can be compiled 20 | . repo_name/.venv/bin/activate && cd repo_name && make fake_translations 21 | 22 | # Ensure documentation can be compiled 23 | . repo_name/.venv/bin/activate && cd repo_name && make doc_requirements 24 | . repo_name/.venv/bin/activate && cd repo_name/docs && make html 25 | 26 | clean: 27 | rm -rf .venv 28 | rm -rf repo_name 29 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/docker-compose.yml: -------------------------------------------------------------------------------- 1 | db: 2 | image: mysql:5.6 3 | container_name: db 4 | environment: 5 | MYSQL_ROOT_PASSWORD: "" 6 | MYSQL_ALLOW_EMPTY_PASSWORD: "yes" 7 | 8 | memcache: 9 | image: memcached:1.4.24 10 | container_name: memcache 11 | 12 | {{cookiecutter.repo_name}}: 13 | # Uncomment this line to use the official {{cookiecutter.repo_name}} base image 14 | image: openedx/{{cookiecutter.repo_name}} 15 | 16 | container_name: {{cookiecutter.repo_name}} 17 | volumes: 18 | - .:/edx/app/{{cookiecutter.repo_name}}/ 19 | command: bash -c 'gunicorn --reload --workers=2 --name {{cookiecutter.repo_name}} -b :{{cookiecutter.port}} -c /edx/app/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/docker_gunicorn_configuration.py --log-file - --max-requests=1000 {{cookiecutter.repo_name}}.wsgi:application' 20 | environment: 21 | DJANGO_SETTINGS_MODULE: {{cookiecutter.repo_name}}.settings.devstack 22 | ports: 23 | - "{{cookiecutter.port}}:{{cookiecutter.port}}" # TODO: change this to your port 24 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | *.log 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Packages 9 | *.egg 10 | *.egg-info 11 | dist 12 | build 13 | eggs 14 | parts 15 | bin 16 | var 17 | sdist 18 | develop-eggs 19 | .installed.cfg 20 | lib 21 | lib64 22 | __pycache__ 23 | 24 | # Installer logs 25 | pip-log.txt 26 | 27 | # Django 28 | default.db 29 | 30 | # Static assets 31 | media/cache/ 32 | assets/ 33 | 34 | # Unit test / coverage reports 35 | .coverage 36 | htmlcov 37 | .tox 38 | nosetests.xml 39 | unittests.xml 40 | 41 | # PII annotation reports 42 | pii_report 43 | 44 | ### Internationalization artifacts 45 | *.mo 46 | *.po 47 | *.prob 48 | !django.po 49 | !django.mo 50 | !djangojs.po 51 | !djangojs.mo 52 | {{cookiecutter.repo_name}}/conf/locale/fake*/LC_MESSAGES/*.po 53 | {{cookiecutter.repo_name}}/conf/locale/fake*/LC_MESSAGES/*.mo 54 | {{cookiecutter.repo_name}}/conf/locale/messages.mo 55 | 56 | 57 | # Mr Developer 58 | .mr.developer.cfg 59 | .project 60 | .pydevproject 61 | 62 | # QA 63 | coverage.xml 64 | diff_*.html 65 | *.report 66 | report 67 | venv 68 | acceptance_tests.*.log 69 | acceptance_tests.*.png 70 | 71 | # Override config files 72 | override.cfg 73 | private.py 74 | 75 | # JetBrains 76 | .idea 77 | 78 | # OS X 79 | .DS_Store 80 | 81 | # Editor Temp Files 82 | *.swp 83 | 84 | *.trace 85 | 86 | docs/_build/ 87 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/docs/features.rst: -------------------------------------------------------------------------------- 1 | Feature Toggling 2 | ================ 3 | All new features/functionality should be released behind a feature gate. This allows us to easily disable features 4 | in the event that an issue is discovered in production. This project uses the 5 | `Waffle `_ library for feature gating. 6 | 7 | Waffle supports three types of feature gates (listed below). We typically use flags and switches since samples are 8 | random, and not ideal for our needs. 9 | 10 | Flag 11 | Enable a feature for specific users, groups, users meeting certain criteria (e.g. authenticated or staff), 12 | or a certain percentage of visitors. 13 | 14 | Switch 15 | Simple boolean, toggling a feature for all users. 16 | 17 | Sample 18 | Toggle the feature for a specified percentage of the time. 19 | 20 | 21 | For information on creating or updating features, refer to the 22 | `Waffle documentation `_. 23 | 24 | Permanent Feature Rollout 25 | ------------------------- 26 | Over time some features may become permanent and no longer need a feature gate around them. In such instances, the 27 | relevant code and tests should be updated to remove the feature gate. Once the code is released, the feature flag/switch 28 | should be deleted. 29 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/.annotation_safe_list.yml: -------------------------------------------------------------------------------- 1 | # This is a Code Annotations automatically-generated Django model safelist file. 2 | # These models must be annotated as follows in order to be counted in the coverage report. 3 | # See https://code-annotations.readthedocs.io/en/latest/safelist.html for more information. 4 | # 5 | # fake_app_1.FakeModelName: 6 | # ".. no_pii:": "This model has no PII" 7 | # fake_app_2.FakeModel2: 8 | # ".. choice_annotation:": foo, bar, baz 9 | 10 | admin.LogEntry: 11 | ".. no_pii:": "This model has no PII" 12 | auth.Group: 13 | ".. no_pii:": "This model has no PII" 14 | auth.Permission: 15 | ".. no_pii:": "This model has no PII" 16 | contenttypes.ContentType: 17 | ".. no_pii:": "This model has no PII" 18 | sessions.Session: 19 | ".. no_pii:": "This model has no PII" 20 | social_django.Association: 21 | ".. no_pii:": "This model has no PII" 22 | social_django.Code: 23 | ".. pii:": "Email address" 24 | ".. pii_types:": other 25 | ".. pii_retirement:": local_api 26 | social_django.Nonce: 27 | ".. no_pii:": "This model has no PII" 28 | social_django.Partial: 29 | ".. no_pii:": "This model has no PII" 30 | social_django.UserSocialAuth: 31 | ".. no_pii:": "This model has no PII" 32 | waffle.Flag: 33 | ".. no_pii:": "This model has no PII" 34 | waffle.Sample: 35 | ".. no_pii:": "This model has no PII" 36 | waffle.Switch: 37 | ".. no_pii:": "This model has no PII" 38 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/core/models.py: -------------------------------------------------------------------------------- 1 | """ Core models. """ 2 | 3 | from django.contrib.auth.models import AbstractUser 4 | from django.db import models 5 | from django.utils.encoding import python_2_unicode_compatible 6 | from django.utils.translation import ugettext_lazy as _ 7 | 8 | 9 | class User(AbstractUser): 10 | """ 11 | Custom user model for use with python-social-auth via edx-auth-backends. 12 | 13 | .. pii: Stores full name, username, and email address for a user. 14 | .. pii_types: name, username, email_address 15 | .. pii_retirement: local_api 16 | 17 | """ 18 | full_name = models.CharField(_('Full Name'), max_length=255, blank=True, null=True) 19 | 20 | @property 21 | def access_token(self): 22 | """ 23 | Returns an OAuth2 access token for this user, if one exists; otherwise None. 24 | Assumes user has authenticated at least once with the OAuth2 provider (LMS). 25 | """ 26 | try: 27 | return self.social_auth.first().extra_data[u'access_token'] # pylint: disable=no-member 28 | except Exception: # pylint: disable=broad-except 29 | return None 30 | 31 | class Meta: 32 | get_latest_by = 'date_joined' 33 | 34 | def get_full_name(self): 35 | return self.full_name or super(User, self).get_full_name() 36 | 37 | @python_2_unicode_compatible 38 | def __str__(self): 39 | return str(self.get_full_name()) 40 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/.pii_annotations.yml: -------------------------------------------------------------------------------- 1 | source_path: ./ 2 | report_path: pii_report 3 | safelist_path: .annotation_safe_list.yml 4 | coverage_target: 100.0 5 | annotations: 6 | ".. no_pii:": 7 | "pii_group": 8 | - ".. pii:": 9 | - ".. pii_types:": 10 | choices: 11 | - id # Unique identifier for the user which is shared across systems 12 | - name # Used for any part of the user’s name 13 | - username 14 | - password 15 | - location # Used for any part of any type address or country stored 16 | - phone_number # Used for phone or fax numbers 17 | - email_address 18 | - birth_date # Used for any part of a stored birth date 19 | - ip # IP address 20 | - external_service # Used for external service ids or links such as social media links or usernames, website links, etc. 21 | - biography # Any type of free-form biography field 22 | - gender 23 | - sex 24 | - image 25 | - video 26 | - other 27 | - ".. pii_retirement:": 28 | choices: 29 | - retained # Intentionally kept for legal reasons 30 | - local_api # An API exists in this repository for retiring this information 31 | - consumer_api # The data's consumer must implement an API for retiring this information 32 | - third_party # A third party API exists to retire this data 33 | extensions: 34 | python: 35 | - py 36 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/settings/devstack.py: -------------------------------------------------------------------------------- 1 | from {{cookiecutter.repo_name}}.settings.local import * 2 | 3 | # Generic OAuth2 variables irrespective of SSO/backend service key types. 4 | OAUTH2_PROVIDER_URL = 'http://edx.devstack.lms:18000/oauth2' 5 | 6 | # OAuth2 variables specific to social-auth/SSO login use case. 7 | SOCIAL_AUTH_EDX_OAUTH2_KEY = os.environ.get('SOCIAL_AUTH_EDX_OAUTH2_KEY', '{{cookiecutter.repo_name}}-sso-key') 8 | SOCIAL_AUTH_EDX_OAUTH2_SECRET = os.environ.get('SOCIAL_AUTH_EDX_OAUTH2_SECRET', '{{cookiecutter.repo_name}}-sso-secret') 9 | SOCIAL_AUTH_EDX_OAUTH2_ISSUER = os.environ.get('SOCIAL_AUTH_EDX_OAUTH2_ISSUER', 'http://localhost:18000') 10 | SOCIAL_AUTH_EDX_OAUTH2_URL_ROOT = os.environ.get('SOCIAL_AUTH_EDX_OAUTH2_URL_ROOT', 'http://edx.devstack.lms:18000') 11 | SOCIAL_AUTH_EDX_OAUTH2_LOGOUT_URL = os.environ.get('SOCIAL_AUTH_EDX_OAUTH2_LOGOUT_URL', 'http://localhost:18000/logout') 12 | SOCIAL_AUTH_EDX_OAUTH2_PUBLIC_URL_ROOT = os.environ.get( 13 | 'SOCIAL_AUTH_EDX_OAUTH2_PUBLIC_URL_ROOT', 'http://localhost:18000', 14 | ) 15 | 16 | # OAuth2 variables specific to backend service API calls. 17 | BACKEND_SERVICE_EDX_OAUTH2_KEY = os.environ.get('BACKEND_SERVICE_EDX_OAUTH2_KEY', '{{cookiecutter.repo_name}}-backend-service-key') 18 | BACKEND_SERVICE_EDX_OAUTH2_SECRET = os.environ.get('BACKEND_SERVICE_EDX_OAUTH2_SECRET', '{{cookiecutter.repo_name}}-backend-service-secret') 19 | 20 | JWT_AUTH.update({ 21 | 'JWT_SECRET_KEY': SOCIAL_AUTH_EDX_OAUTH2_SECRET, 22 | 'JWT_ISSUER': 'http://localhost:18000/oauth2', 23 | 'JWT_AUDIENCE': SOCIAL_AUTH_EDX_OAUTH2_KEY, 24 | }) 25 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/core/tests/test_models.py: -------------------------------------------------------------------------------- 1 | """ Tests for core models. """ 2 | 3 | from django.test import TestCase 4 | from django_dynamic_fixture import G 5 | from social_django.models import UserSocialAuth 6 | 7 | from {{cookiecutter.repo_name}}.apps.core.models import User 8 | 9 | 10 | class UserTests(TestCase): 11 | """ User model tests. """ 12 | TEST_CONTEXT = {'foo': 'bar', 'baz': None} 13 | 14 | def test_access_token(self): 15 | user = G(User) 16 | self.assertIsNone(user.access_token) 17 | 18 | social_auth = G(UserSocialAuth, user=user) 19 | self.assertIsNone(user.access_token) 20 | 21 | access_token = 'My voice is my passport. Verify me.' 22 | social_auth.extra_data['access_token'] = access_token 23 | social_auth.save() 24 | self.assertEqual(user.access_token, access_token) 25 | 26 | def test_get_full_name(self): 27 | """ Test that the user model concatenates first and last name if the full name is not set. """ 28 | full_name = 'George Costanza' 29 | user = G(User, full_name=full_name) 30 | self.assertEqual(user.get_full_name(), full_name) 31 | 32 | first_name = 'Jerry' 33 | last_name = 'Seinfeld' 34 | user = G(User, full_name=None, first_name=first_name, last_name=last_name) 35 | expected = '{first_name} {last_name}'.format(first_name=first_name, last_name=last_name) 36 | self.assertEqual(user.get_full_name(), expected) 37 | 38 | user = G(User, full_name=full_name, first_name=first_name, last_name=last_name) 39 | self.assertEqual(user.get_full_name(), full_name) 40 | 41 | def test_string(self): 42 | """Verify that the model's string method returns the user's full name.""" 43 | full_name = 'Bob' 44 | user = G(User, full_name=full_name) 45 | self.assertEqual(str(user), full_name) 46 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/urls.py: -------------------------------------------------------------------------------- 1 | """{{ cookiecutter.repo_name }} URL Configuration 2 | The `urlpatterns` list routes URLs to views. For more information please see: 3 | https://docs.djangoproject.com/en/1.11/topics/http/urls/ 4 | Examples: 5 | Function views 6 | 1. Add an import: from my_app import views 7 | 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') 8 | Class-based views 9 | 1. Add an import: from other_app.views import Home 10 | 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') 11 | Including another URLconf 12 | 1. Add an import: from blog import urls as blog_urls 13 | 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) 14 | """ 15 | 16 | import os 17 | 18 | from auth_backends.urls import oauth2_urlpatterns 19 | from django.conf import settings 20 | from django.conf.urls import include, url 21 | from django.contrib import admin 22 | from rest_framework_swagger.views import get_swagger_view 23 | 24 | from {{cookiecutter.repo_name}}.apps.api import urls as api_urls 25 | from {{cookiecutter.repo_name}}.apps.core import views as core_views 26 | 27 | 28 | admin.autodiscover() 29 | 30 | urlpatterns = [ 31 | url(r'^admin/', admin.site.urls), 32 | url(r'^api/', include(api_urls)), 33 | url(r'^api-docs/', get_swagger_view(title='{{cookiecutter.repo_name}} API')), 34 | # Use the same auth views for all logins, including those originating from the browseable API. 35 | url(r'^api-auth/', include(oauth2_urlpatterns)), 36 | url(r'^auto_auth/$', core_views.AutoAuth.as_view(), name='auto_auth'), 37 | url(r'^health/$', core_views.health, name='health'), 38 | ] 39 | 40 | if settings.DEBUG and os.environ.get('ENABLE_DJANGO_TOOLBAR', False): # pragma: no cover 41 | # Disable pylint import error because we don't install django-debug-toolbar 42 | # for CI build 43 | import debug_toolbar # pylint: disable=import-error 44 | urlpatterns.append(url(r'^__debug__/', include(debug_toolbar.urls))) 45 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/docker_gunicorn_configuration.py: -------------------------------------------------------------------------------- 1 | """ 2 | gunicorn configuration file: http://docs.gunicorn.org/en/develop/configure.html 3 | """ 4 | import multiprocessing # pylint: disable=unused-import 5 | 6 | 7 | preload_app = True 8 | timeout = 300 9 | bind = "0.0.0.0:{{cookiecutter.port}}" 10 | 11 | workers = 2 12 | 13 | 14 | def pre_request(worker, req): 15 | worker.log.info("%s %s" % (req.method, req.path)) 16 | 17 | 18 | def close_all_caches(): 19 | """ 20 | Close the cache so that newly forked workers cannot accidentally share 21 | the socket with the processes they were forked from. This prevents a race 22 | condition in which one worker could get a cache response intended for 23 | another worker. 24 | """ 25 | # We do this in a way that is safe for 1.4 and 1.8 while we still have some 26 | # 1.4 installations. 27 | from django.conf import settings 28 | from django.core import cache as django_cache 29 | if hasattr(django_cache, 'caches'): 30 | get_cache = django_cache.caches.__getitem__ 31 | else: 32 | get_cache = django_cache.get_cache # pylint: disable=no-member 33 | for cache_name in settings.CACHES: 34 | cache = get_cache(cache_name) 35 | if hasattr(cache, 'close'): 36 | cache.close() 37 | 38 | # The 1.4 global default cache object needs to be closed also: 1.4 39 | # doesn't ensure you get the same object when requesting the same 40 | # cache. The global default is a separate Python object from the cache 41 | # you get with get_cache("default"), so it will have its own connection 42 | # that needs to be closed. 43 | cache = django_cache.cache 44 | if hasattr(cache, 'close'): 45 | cache.close() 46 | 47 | 48 | def post_fork(server, worker): # pylint: disable=unused-argument 49 | close_all_caches() 50 | 51 | def when_ready(server): # pylint: disable=unused-argument 52 | """When running in debug mode, run Django's `check` to better match what `manage.py runserver` does""" 53 | from django.conf import settings 54 | from django.core.management import call_command 55 | if settings.DEBUG: 56 | call_command("check") 57 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/settings/production.py: -------------------------------------------------------------------------------- 1 | from os import environ 2 | import yaml 3 | 4 | from {{cookiecutter.repo_name}}.settings.base import * 5 | from {{cookiecutter.repo_name}}.settings.utils import get_env_setting 6 | 7 | 8 | DEBUG = False 9 | TEMPLATE_DEBUG = DEBUG 10 | 11 | ALLOWED_HOSTS = ['*'] 12 | 13 | LOGGING = get_logger_config() 14 | 15 | # Keep track of the names of settings that represent dicts. Instead of overriding the values in base.py, 16 | # the values read from disk should UPDATE the pre-configured dicts. 17 | DICT_UPDATE_KEYS = ('JWT_AUTH',) 18 | 19 | # This may be overridden by the YAML in {{cookiecutter.repo_name}}_CFG, 20 | # but it should be here as a default. 21 | MEDIA_STORAGE_BACKEND = {} 22 | FILE_STORAGE_BACKEND = {} 23 | 24 | CONFIG_FILE = get_env_setting('{{cookiecutter.repo_name|upper}}_CFG') 25 | with open(CONFIG_FILE, encoding='utf-8') as f: 26 | config_from_yaml = yaml.load(f) 27 | 28 | # Remove the items that should be used to update dicts, and apply them separately rather 29 | # than pumping them into the local vars. 30 | dict_updates = {key: config_from_yaml.pop(key, None) for key in DICT_UPDATE_KEYS} 31 | 32 | for key, value in dict_updates.items(): 33 | if value: 34 | vars()[key].update(value) 35 | 36 | vars().update(config_from_yaml) 37 | 38 | # Unpack the media and files storage backend settings for django storages. 39 | # These dicts are not Django settings themselves, but they contain a mapping 40 | # of Django settings. 41 | vars().update(FILE_STORAGE_BACKEND) 42 | vars().update(MEDIA_STORAGE_BACKEND) 43 | 2 44 | 45 | DB_OVERRIDES = dict( 46 | PASSWORD=environ.get('DB_MIGRATION_PASS', DATABASES['default']['PASSWORD']), 47 | ENGINE=environ.get('DB_MIGRATION_ENGINE', DATABASES['default']['ENGINE']), 48 | USER=environ.get('DB_MIGRATION_USER', DATABASES['default']['USER']), 49 | NAME=environ.get('DB_MIGRATION_NAME', DATABASES['default']['NAME']), 50 | HOST=environ.get('DB_MIGRATION_HOST', DATABASES['default']['HOST']), 51 | PORT=environ.get('DB_MIGRATION_PORT', DATABASES['default']['PORT']), 52 | ) 53 | 54 | for override, value in DB_OVERRIDES.items(): 55 | DATABASES['default'][override] = value 56 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/settings/local.py: -------------------------------------------------------------------------------- 1 | from {{cookiecutter.repo_name}}.settings.base import * 2 | 3 | DEBUG = True 4 | 5 | # CACHE CONFIGURATION 6 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#caches 7 | CACHES = { 8 | 'default': { 9 | 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 10 | } 11 | } 12 | # END CACHE CONFIGURATION 13 | 14 | # DATABASE CONFIGURATION 15 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#databases 16 | DATABASES = { 17 | 'default': { 18 | 'ENGINE': 'django.db.backends.sqlite3', 19 | 'NAME': root('default.db'), 20 | 'USER': '', 21 | 'PASSWORD': '', 22 | 'HOST': '', 23 | 'PORT': '', 24 | } 25 | } 26 | # END DATABASE CONFIGURATION 27 | 28 | # EMAIL CONFIGURATION 29 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend 30 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 31 | # END EMAIL CONFIGURATION 32 | 33 | # TOOLBAR CONFIGURATION 34 | # See: http://django-debug-toolbar.readthedocs.org/en/latest/installation.html 35 | if os.environ.get('ENABLE_DJANGO_TOOLBAR', False): 36 | INSTALLED_APPS += ( 37 | 'debug_toolbar', 38 | ) 39 | 40 | MIDDLEWARE_CLASSES += ( 41 | 'debug_toolbar.middleware.DebugToolbarMiddleware', 42 | ) 43 | 44 | DEBUG_TOOLBAR_PATCH_SETTINGS = False 45 | 46 | INTERNAL_IPS = ('127.0.0.1',) 47 | # END TOOLBAR CONFIGURATION 48 | 49 | # AUTHENTICATION 50 | # Use a non-SSL URL for authorization redirects 51 | SOCIAL_AUTH_REDIRECT_IS_HTTPS = False 52 | 53 | # Generic OAuth2 variables irrespective of SSO/backend service key types. 54 | OAUTH2_PROVIDER_URL = 'http://localhost:18000/oauth2' 55 | 56 | JWT_AUTH.update({ 57 | 'JWT_ALGORITHM': 'HS256', 58 | 'JWT_SECRET_KEY': SOCIAL_AUTH_EDX_OAUTH2_SECRET, 59 | 'JWT_ISSUER': OAUTH2_PROVIDER_URL, 60 | 'JWT_AUDIENCE': SOCIAL_AUTH_EDX_OAUTH2_KEY, 61 | }) 62 | 63 | ENABLE_AUTO_AUTH = True 64 | 65 | LOGGING = get_logger_config(debug=DEBUG, dev_env=True, local_loglevel='DEBUG') 66 | 67 | ##################################################################### 68 | # Lastly, see if the developer has any local overrides. 69 | if os.path.isfile(join(dirname(abspath(__file__)), 'private.py')): 70 | from .private import * # pylint: disable=import-error 71 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | TODO: Include a detailed description of the changes in the body of the PR 4 | 5 | ## Manual Test Plan 6 | 7 | TODO: Include instructions for any required manual tests, and any manual testing that has 8 | already been performed. 9 | 10 | ## Testing Checklist 11 | - TODO: Include unit, integration, acceptance tests as appropriate 12 | - TODO: Include accessibility (a11y) tests 13 | - TODO: Include tests that capture the external-query scaling properties 14 | - [ ] Check that Database migrations are backwards-compatible 15 | - [ ] Manually test right-to-left languages and i18n 16 | of the changes. 17 | 18 | ## Non-testing Checklist 19 | - TODO: Tag DevOps on your PR (user `edx/devops`), either for review 20 | (more specific to devops = better) or just a general FYI. 21 | - TODO: Consider any documentation your change might need, and which 22 | users will be affected by this change. 23 | - TODO: Double-check that your commit messages will make a meaningful release note. 24 | 25 | ## Post-review 26 | - TODO: Squash commits into discrete sets of changes (see the note about release notes above) 27 | 28 | ## Reviewers 29 | If you've been tagged for review, please check your corresponding box once you've given the :+1:. 30 | - [ ] Code review #1 (TODO: tag a specific user) 31 | - [ ] Code review #2 (TODO: tag a specific user) 32 | - [ ] Docs review (required for UI strings/error messages) 33 | - [ ] UX review 34 | - [ ] Accessibility review 35 | - [ ] Product review 36 | 37 | ### Areas to Consider 38 | - [ ] i18n 39 | - Are all user-facing strings tagged? 40 | - [ ] Right-to-left 41 | - Will the feature work for right-to-left languages? 42 | - [ ] Analytics 43 | - Are any new events being emitted? 44 | - Have any events been changed? 45 | - Are there any new user actions that should be emitted as events? 46 | - [ ] Performance 47 | - What dimensions does this change scale over? Users? Courses? Course size? 48 | - How does the feature scale over those dimensions? Sub-linear? Linear? Quadratic? Exponential? 49 | - How does the scaling affect the number of external calls (database queries, 50 | api requests, etc) that this code makes? 51 | - [ ] Database migrations 52 | - Are they backwards compatible? 53 | - When they run on production, how long will they take? Will they lock the table? 54 | 55 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/docs/internationalization.rst: -------------------------------------------------------------------------------- 1 | Internationalization 2 | ==================== 3 | All user-facing text content should be marked for translation. Even if this application is only run in English, our 4 | open source users may choose to use another language. Marking content for translation ensures our users have 5 | this choice. 6 | 7 | Follow the `internationalization coding guidelines`_ in the edX Developer's Guide when developing new features. 8 | 9 | .. _internationalization coding guidelines: http://edx.readthedocs.org/projects/edx-developer-guide/en/latest/internationalization/i18n.html 10 | 11 | Updating Translations 12 | ~~~~~~~~~~~~~~~~~~~~~ 13 | This project uses `Transifex`_ to translate content. After new features are developed the translation source files 14 | should be pushed to Transifex. Our translation community will translate the content, after which we can retrieve the 15 | translations. 16 | 17 | .. _Transifex: https://www.transifex.com/ 18 | 19 | Pushing source translation files to Transifex requires access to the edx-platform. Request access from the Open Source 20 | Team if you will be pushing translation files. You should also `configure the Transifex client`_ if you have not done so 21 | already. 22 | 23 | .. _configure the Transifex client: http://docs.transifex.com/client/config/ 24 | 25 | The `make` targets listed below can be used to push or pull translations. 26 | 27 | .. list-table:: 28 | :widths: 25 75 29 | :header-rows: 1 30 | 31 | * - Target 32 | - Description 33 | * - pull_translations 34 | - Pull translations from Transifex 35 | * - push_translations 36 | - Push source translation files to Transifex 37 | 38 | Fake Translations 39 | ~~~~~~~~~~~~~~~~~ 40 | As you develop features it may be helpful to know which strings have been marked for translation, and which are not. 41 | Use the `fake_translations` make target for this purpose. This target will extract all strings marked for translation, 42 | generate fake translations in the Esperanto (eo) language directory, and compile the translations. 43 | 44 | You can trigger the display of the translations by setting your browser's language to Esperanto (eo), and navigating to 45 | a page on the site. Instead of plain English strings, you should see specially-accented English strings that look like this: 46 | 47 | Thé Fütüré øf Ønlïné Édüçätïøn Ⱡσяєм ι# Før änýøné, änýwhéré, änýtïmé Ⱡσяєм # 48 | 49 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/README.rst: -------------------------------------------------------------------------------- 1 | {{cookiecutter.project_name}} |Travis|_ |Codecov|_ 2 | =================================================== 3 | .. |Travis| image:: https://travis-ci.org/edx/{{cookiecutter.repo_name}}.svg?branch=master 4 | .. _Travis: https://travis-ci.org/edx/{{cookiecutter.repo_name}} 5 | 6 | .. |Codecov| image:: http://codecov.io/github/edx/{{cookiecutter.repo_name}}/coverage.svg?branch=master 7 | .. _Codecov: http://codecov.io/github/edx/{{cookiecutter.repo_name}}?branch=master 8 | 9 | The ``README.rst`` file should start with a brief description of the repository, which sets it in the context of other repositories under the ``edx`` organization. It should make clear where this fits in to the overall edX codebase. You may also want to provide a brief overview of the code in this repository, including the main components and useful entry points for starting to understand the code in more detail, or link to a comparable description in your repo's docs. 10 | 11 | Documentation 12 | ------------- 13 | .. |ReadtheDocs| image:: https://readthedocs.org/projects/{{cookiecutter.repo_name}}/badge/?version=latest 14 | .. _ReadtheDocs: http://{{cookiecutter.repo_name}}.readthedocs.io/en/latest/ 15 | 16 | `Documentation `_ is hosted on Read the Docs. The source is hosted in this repo's `docs `_ directory. To contribute, please open a PR against this repo. 17 | 18 | License 19 | ------- 20 | 21 | The code in this repository is licensed under version 3 of the AGPL unless otherwise noted. Please see the LICENSE_ file for details. 22 | 23 | .. _LICENSE: https://github.com/edx/{{cookiecutter.repo_name}}/blob/master/LICENSE 24 | 25 | How To Contribute 26 | ----------------- 27 | 28 | Contributions are welcome. Please read `How To Contribute `_ for details. Even though it was written with ``edx-platform`` in mind, these guidelines should be followed for Open edX code in general. 29 | 30 | Reporting Security Issues 31 | ------------------------- 32 | 33 | Please do not report security issues in public. Please email security@edx.org. 34 | 35 | Get Help 36 | -------- 37 | 38 | Ask questions and discuss this project on `Slack `_ or in the `edx-code Google Group `_. 39 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 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 .editorconfig_tweaks file to add changes just to this 14 | # repo's file. 15 | # 16 | # 2. Run: 17 | # 18 | # $ edx_lint write .editorconfig 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 .editorconfig file in the edx-lint repo at 27 | # https://github.com/edx/edx-lint/blob/master/edx_lint/files/.editorconfig 28 | # 29 | # 2. install the updated version of edx-lint (in edx-lint): 30 | # 31 | # $ pip install . 32 | # 33 | # 3. Run (in edx-lint): 34 | # 35 | # # uses .editorconfig_tweaks from edx-lint for linting in edx-lint 36 | # # NOTE: Use Python 3.x, which no longer includes comments in the output file 37 | # $ edx_lint write .editorconfig 38 | # 39 | # 4. Make a new version of edx_lint, submit and review a pull request with the 40 | # .editorconfig update, and after merging, update the edx-lint version by 41 | # creating a new tag in the repo (uses pbr). 42 | # 43 | # 5. In your local repo, install the newer version of edx-lint. 44 | # 45 | # 6. Run: 46 | # 47 | # # uses local .editorconfig_tweaks 48 | # $ edx_lint write .editorconfig 49 | # 50 | # 7. This will modify the local file. Submit a pull request to get it 51 | # checked in so that others will benefit. 52 | # 53 | # 54 | # 55 | # 56 | # 57 | # STAY AWAY FROM THIS FILE! 58 | # 59 | # 60 | # 61 | # 62 | # 63 | # SERIOUSLY. 64 | # 65 | # ------------------------------ 66 | [*] 67 | end_of_line = lf 68 | insert_final_newline = true 69 | charset = utf-8 70 | indent_style = space 71 | indent_size = 4 72 | max_line_length = 120 73 | trim_trailing_whitespace = true 74 | 75 | [{Makefile, *.mk}] 76 | indent_style = tab 77 | indent_size = 8 78 | 79 | [*.{yml,yaml,json}] 80 | indent_size = 2 81 | 82 | [*.js] 83 | indent_size = 2 84 | 85 | [*.diff] 86 | trim_trailing_whitespace = false 87 | 88 | [.git/*] 89 | trim_trailing_whitespace = false 90 | 91 | [*.rst] 92 | max_line_length = 79 93 | 94 | # 01d24285b953f74272f86b1e42a0235315085e59 95 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/.editorconfig: -------------------------------------------------------------------------------- 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 .editorconfig_tweaks file to add changes just to this 14 | # repo's file. 15 | # 16 | # 2. Run: 17 | # 18 | # $ edx_lint write .editorconfig 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 .editorconfig file in the edx-lint repo at 27 | # https://github.com/edx/edx-lint/blob/master/edx_lint/files/.editorconfig 28 | # 29 | # 2. install the updated version of edx-lint (in edx-lint): 30 | # 31 | # $ pip install . 32 | # 33 | # 3. Run (in edx-lint): 34 | # 35 | # # uses .editorconfig_tweaks from edx-lint for linting in edx-lint 36 | # # NOTE: Use Python 3.x, which no longer includes comments in the output file 37 | # $ edx_lint write .editorconfig 38 | # 39 | # 4. Make a new version of edx_lint, submit and review a pull request with the 40 | # .editorconfig update, and after merging, update the edx-lint version by 41 | # creating a new tag in the repo (uses pbr). 42 | # 43 | # 5. In your local repo, install the newer version of edx-lint. 44 | # 45 | # 6. Run: 46 | # 47 | # # uses local .editorconfig_tweaks 48 | # $ edx_lint write .editorconfig 49 | # 50 | # 7. This will modify the local file. Submit a pull request to get it 51 | # checked in so that others will benefit. 52 | # 53 | # 54 | # 55 | # 56 | # 57 | # STAY AWAY FROM THIS FILE! 58 | # 59 | # 60 | # 61 | # 62 | # 63 | # SERIOUSLY. 64 | # 65 | # ------------------------------ 66 | [*] 67 | end_of_line = lf 68 | insert_final_newline = true 69 | charset = utf-8 70 | indent_style = space 71 | indent_size = 4 72 | max_line_length = 120 73 | trim_trailing_whitespace = true 74 | 75 | [{Makefile, *.mk}] 76 | indent_style = tab 77 | indent_size = 8 78 | 79 | [*.{yml,yaml,json}] 80 | indent_size = 2 81 | 82 | [*.js] 83 | indent_size = 2 84 | 85 | [*.diff] 86 | trim_trailing_whitespace = false 87 | 88 | [.git/*] 89 | trim_trailing_whitespace = false 90 | 91 | [*.rst] 92 | max_line_length = 79 93 | 94 | # 01d24285b953f74272f86b1e42a0235315085e59 95 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/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 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:bionic as app 2 | MAINTAINER devops@edx.org 3 | 4 | 5 | # Packages installed: 6 | # git; Used to pull in particular requirements from github rather than pypi, 7 | # and to check the sha of the code checkout. 8 | 9 | # language-pack-en locales; ubuntu locale support so that system utilities have a consistent 10 | # language and time zone. 11 | 12 | # python; ubuntu doesnt ship with python, so this is the python we will use to run the application 13 | 14 | # python3-pip; install pip to install application requirements.txt files 15 | 16 | # libssl-dev; # mysqlclient wont install without this. 17 | 18 | # libmysqlclient-dev; to install header files needed to use native C implementation for 19 | # MySQL-python for performance gains. 20 | 21 | # If you add a package here please include a comment above describing what it is used for 22 | RUN apt-get update && apt-get upgrade -qy && apt-get install language-pack-en locales git python3.5 python3-pip libmysqlclient-dev libssl-dev python3-dev -qy && \ 23 | pip3 install --upgrade pip setuptools && \ 24 | rm -rf /var/lib/apt/lists/* 25 | 26 | RUN ln -s /usr/bin/pip3 /usr/bin/pip 27 | RUN ln -s /usr/bin/python3 /usr/bin/python 28 | 29 | RUN locale-gen en_US.UTF-8 30 | ENV LANG en_US.UTF-8 31 | ENV LANGUAGE en_US:en 32 | ENV LC_ALL en_US.UTF-8 33 | ENV DJANGO_SETTINGS_MODULE {{cookiecutter.repo_name}}.settings.production 34 | 35 | EXPOSE {{cookiecutter.port}} 36 | RUN useradd -m --shell /bin/false app 37 | 38 | WORKDIR /edx/app/{{cookiecutter.repo_name}} 39 | 40 | # Copy the requirements explicitly even though we copy everything below 41 | # this prevents the image cache from busting unless the dependencies have changed. 42 | COPY requirements/production.txt /edx/app/{{cookiecutter.repo_name}}/requirements/production.txt 43 | 44 | # Dependencies are installed as root so they cannot be modified by the application user. 45 | RUN pip3 install -r requirements/production.txt 46 | 47 | RUN mkdir -p /edx/var/log 48 | 49 | # Code is owned by root so it cannot be modified by the application user. 50 | # So we copy it before changing users. 51 | USER app 52 | 53 | # Gunicorn 19 does not log to stdout or stderr by default. Once we are past gunicorn 19, the logging to STDOUT need not be specified. 54 | CMD gunicorn --workers=2 --name {{cookiecutter.repo_name}} -c /edx/app/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/docker_gunicorn_configuration.py --log-file - --max-requests=1000 {{cookiecutter.repo_name}}.wsgi:application 55 | 56 | # This line is after the requirements so that changes to the code will not 57 | # bust the image cache 58 | COPY . /edx/app/{{cookiecutter.repo_name}} 59 | 60 | 61 | FROM app as newrelic 62 | RUN pip install newrelic 63 | CMD newrelic-admin run-program gunicorn --workers=2 --name {{cookiecutter.repo_name}} -c /edx/app/{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/docker_gunicorn_configuration.py --log-file - --max-requests=1000 {{cookiecutter.repo_name}}.wsgi:application 64 | 65 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/core/views.py: -------------------------------------------------------------------------------- 1 | """ Core views. """ 2 | import logging 3 | import uuid 4 | 5 | from django.conf import settings 6 | from django.contrib.auth import authenticate, get_user_model, login 7 | from django.db import DatabaseError, connection, transaction 8 | from django.http import Http404, JsonResponse 9 | from django.shortcuts import redirect 10 | from django.views.generic import View 11 | 12 | from {{cookiecutter.repo_name}}.apps.core.constants import Status 13 | 14 | 15 | logger = logging.getLogger(__name__) 16 | User = get_user_model() 17 | 18 | 19 | @transaction.non_atomic_requests 20 | def health(_): 21 | """Allows a load balancer to verify this service is up. 22 | 23 | Checks the status of the database connection on which this service relies. 24 | 25 | Returns: 26 | HttpResponse: 200 if the service is available, with JSON data indicating the health of each required service 27 | HttpResponse: 503 if the service is unavailable, with JSON data indicating the health of each required service 28 | 29 | Example: 30 | >>> response = requests.get('https://{{cookiecutter.repo_name}}.edx.org/health') 31 | >>> response.status_code 32 | 200 33 | >>> response.content 34 | '{"overall_status": "OK", "detailed_status": {"database_status": "OK", "lms_status": "OK"}}' 35 | """ 36 | 37 | try: 38 | cursor = connection.cursor() 39 | cursor.execute("SELECT 1") 40 | cursor.fetchone() 41 | cursor.close() 42 | database_status = Status.OK 43 | except DatabaseError: 44 | database_status = Status.UNAVAILABLE 45 | 46 | overall_status = Status.OK if (database_status == Status.OK) else Status.UNAVAILABLE 47 | 48 | data = { 49 | 'overall_status': overall_status, 50 | 'detailed_status': { 51 | 'database_status': database_status, 52 | }, 53 | } 54 | 55 | if overall_status == Status.OK: 56 | return JsonResponse(data) 57 | else: 58 | return JsonResponse(data, status=503) 59 | 60 | 61 | class AutoAuth(View): 62 | """Creates and authenticates a new User with superuser permissions. 63 | 64 | If the ENABLE_AUTO_AUTH setting is not True, returns a 404. 65 | """ 66 | 67 | def get(self, request): 68 | """ 69 | Create a new User. 70 | 71 | Raises Http404 if auto auth is not enabled. 72 | """ 73 | if not getattr(settings, 'ENABLE_AUTO_AUTH', None): 74 | raise Http404 75 | 76 | username_prefix = getattr(settings, 'AUTO_AUTH_USERNAME_PREFIX', 'auto_auth_') 77 | 78 | # Create a new user with staff permissions 79 | username = password = username_prefix + uuid.uuid4().hex[0:20] 80 | User.objects.create_superuser(username, email=None, password=password) 81 | 82 | # Log in the new user 83 | user = authenticate(username=username, password=password) 84 | login(request, user) 85 | 86 | return redirect('/') 87 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/apps/core/tests/test_views.py: -------------------------------------------------------------------------------- 1 | """Test core.views.""" 2 | 3 | import mock 4 | from django.conf import settings 5 | from django.contrib.auth import get_user_model 6 | from django.db import DatabaseError 7 | from django.test import TestCase 8 | from django.test.utils import override_settings 9 | from django.urls import reverse 10 | 11 | from {{cookiecutter.repo_name}}.apps.core.constants import Status 12 | 13 | 14 | User = get_user_model() 15 | 16 | 17 | class HealthTests(TestCase): 18 | """Tests of the health endpoint.""" 19 | 20 | def test_all_services_available(self): 21 | """Test that the endpoint reports when all services are healthy.""" 22 | self._assert_health(200, Status.OK, Status.OK) 23 | 24 | def test_database_outage(self): 25 | """Test that the endpoint reports when the database is unavailable.""" 26 | with mock.patch('django.db.backends.base.base.BaseDatabaseWrapper.cursor', side_effect=DatabaseError): 27 | self._assert_health(503, Status.UNAVAILABLE, Status.UNAVAILABLE) 28 | 29 | def _assert_health(self, status_code, overall_status, database_status): 30 | """Verify that the response matches expectations.""" 31 | response = self.client.get(reverse('health')) 32 | self.assertEqual(response.status_code, status_code) 33 | self.assertEqual(response['content-type'], 'application/json') 34 | 35 | expected_data = { 36 | 'overall_status': overall_status, 37 | 'detailed_status': { 38 | 'database_status': database_status 39 | } 40 | } 41 | 42 | self.assertJSONEqual(response.content, expected_data) 43 | 44 | 45 | class AutoAuthTests(TestCase): 46 | """ Auto Auth view tests. """ 47 | AUTO_AUTH_PATH = reverse('auto_auth') 48 | 49 | @override_settings(ENABLE_AUTO_AUTH=False) 50 | def test_setting_disabled(self): 51 | """When the ENABLE_AUTO_AUTH setting is False, the view should raise a 404.""" 52 | response = self.client.get(self.AUTO_AUTH_PATH) 53 | self.assertEqual(response.status_code, 404) 54 | 55 | @override_settings(ENABLE_AUTO_AUTH=True) 56 | def test_setting_enabled(self): 57 | """ 58 | When ENABLE_AUTO_AUTH is set to True, the view should create and authenticate 59 | a new User with superuser permissions. 60 | """ 61 | original_user_count = User.objects.count() 62 | response = self.client.get(self.AUTO_AUTH_PATH) 63 | 64 | # Verify that a redirect has occured and that a new user has been created 65 | self.assertEqual(response.status_code, 302) 66 | self.assertEqual(User.objects.count(), original_user_count + 1) 67 | 68 | # Get the latest user 69 | user = User.objects.latest() 70 | 71 | # Verify that the user is logged in and that their username has the expected prefix 72 | self.assertEqual(int(self.client.session['_auth_user_id']), user.pk) 73 | self.assertTrue(user.username.startswith(settings.AUTO_AUTH_USERNAME_PREFIX)) 74 | 75 | # Verify that the user has superuser permissions 76 | self.assertTrue(user.is_superuser) 77 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/settings/utils.py: -------------------------------------------------------------------------------- 1 | import platform 2 | import sys 3 | from logging.handlers import SysLogHandler 4 | from os import environ, path 5 | 6 | from django.core.exceptions import ImproperlyConfigured 7 | 8 | 9 | def get_env_setting(setting): 10 | """ Get the environment setting or raise exception """ 11 | try: 12 | return environ[setting] 13 | except KeyError: 14 | error_msg = "Set the [%s] env variable!" % setting 15 | raise ImproperlyConfigured(error_msg) 16 | 17 | def get_logger_config(log_dir='/var/tmp', 18 | logging_env="no_env", 19 | edx_filename="edx.log", 20 | dev_env=False, 21 | debug=False, 22 | local_loglevel='INFO', 23 | service_variant='{{cookiecutter.repo_name}}'): 24 | """ 25 | Return the appropriate logging config dictionary. You should assign the 26 | result of this to the LOGGING var in your settings. 27 | If dev_env is set to true logging will not be done via local rsyslogd, 28 | instead, application logs will be dropped in log_dir. 29 | "edx_filename" is ignored unless dev_env is set to true since otherwise logging is handled by rsyslogd. 30 | """ 31 | 32 | # Revert to INFO if an invalid string is passed in 33 | if local_loglevel not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']: 34 | local_loglevel = 'INFO' 35 | 36 | hostname = platform.node().split(".")[0] 37 | syslog_format = ( 38 | "[service_variant={service_variant}]" 39 | "[%(name)s][env:{logging_env}] %(levelname)s " 40 | "[{hostname} %(process)d] [%(filename)s:%(lineno)d] " 41 | "- %(message)s" 42 | ).format( 43 | service_variant=service_variant, 44 | logging_env=logging_env, hostname=hostname 45 | ) 46 | 47 | if debug: 48 | handlers = ['console'] 49 | else: 50 | handlers = ['local'] 51 | 52 | logger_config = { 53 | 'version': 1, 54 | 'disable_existing_loggers': False, 55 | 'formatters': { 56 | 'standard': { 57 | 'format': '%(asctime)s %(levelname)s %(process)d ' 58 | '[%(name)s] %(filename)s:%(lineno)d - %(message)s', 59 | }, 60 | 'syslog_format': {'format': syslog_format}, 61 | 'raw': {'format': '%(message)s'}, 62 | }, 63 | 'handlers': { 64 | 'console': { 65 | 'level': 'DEBUG' if debug else 'INFO', 66 | 'class': 'logging.StreamHandler', 67 | 'formatter': 'standard', 68 | 'stream': sys.stdout, 69 | }, 70 | }, 71 | 'loggers': { 72 | 'django': { 73 | 'handlers': handlers, 74 | 'propagate': True, 75 | 'level': 'INFO' 76 | }, 77 | 'requests': { 78 | 'handlers': handlers, 79 | 'propagate': True, 80 | 'level': 'WARNING' 81 | }, 82 | 'factory': { 83 | 'handlers': handlers, 84 | 'propagate': True, 85 | 'level': 'WARNING' 86 | }, 87 | 'django.request': { 88 | 'handlers': handlers, 89 | 'propagate': True, 90 | 'level': 'WARNING' 91 | }, 92 | '': { 93 | 'handlers': handlers, 94 | 'level': 'DEBUG', 95 | 'propagate': False 96 | }, 97 | } 98 | } 99 | 100 | if dev_env: 101 | edx_file_loc = path.join(log_dir, edx_filename) 102 | logger_config['handlers'].update({ 103 | 'local': { 104 | 'class': 'logging.handlers.RotatingFileHandler', 105 | 'level': local_loglevel, 106 | 'formatter': 'standard', 107 | 'filename': edx_file_loc, 108 | 'maxBytes': 1024 * 1024 * 2, 109 | 'backupCount': 5, 110 | }, 111 | }) 112 | else: 113 | logger_config['handlers'].update({ 114 | 'local': { 115 | 'level': local_loglevel, 116 | 'class': 'logging.handlers.SysLogHandler', 117 | # Use a different address for Mac OS X 118 | 'address': '/var/run/syslog' if sys.platform == "darwin" else '/dev/log', 119 | 'formatter': 'syslog_format', 120 | 'facility': SysLogHandler.LOG_LOCAL0, 121 | }, 122 | }) 123 | 124 | return logger_config 125 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/docs/getting_started.rst: -------------------------------------------------------------------------------- 1 | Getting Started 2 | =============== 3 | 4 | If you have not already done so, create/activate a `virtualenv`_. Unless otherwise stated, assume all terminal code 5 | below is executed within the virtualenv. 6 | 7 | .. _virtualenv: https://virtualenvwrapper.readthedocs.org/en/latest/ 8 | 9 | 10 | Install dependencies 11 | -------------------- 12 | Dependencies can be installed via the command below. 13 | 14 | .. code-block:: bash 15 | 16 | $ make requirements 17 | 18 | 19 | Local/Private Settings 20 | ---------------------- 21 | When developing locally, it may be useful to have settings overrides that you do not wish to commit to the repository. 22 | If you need such overrides, create a file :file:`{{cookiecutter.repo_name}}/settings/private.py`. This file's values are 23 | read by :file:`{{cookiecutter.repo_name}}/settings/local.py`, but ignored by Git. 24 | 25 | Configure edX OAuth (Optional) 26 | ------------------------------- 27 | 28 | OAuth only needs to be configured if the IDA would like to use the LMS's authentication functionality in place of managing its own. 29 | 30 | This functionality relies on the LMS server as the OAuth 2.0 authentication provider. 31 | 32 | Configuring {{ cookiecutter.project_name }} to communicate with other IDAs using OAuth requires registering a new client with the authentication 33 | provider (LMS) and updating the Django settings for this project with the generated client credentials. 34 | 35 | A new OAuth 2.0 client can be created when using Devstack by visiting ``http://127.0.0.1:18000/admin/oauth2_provider/application/``. 36 | 1. Click the :guilabel:`Add Application` button. 37 | 2. Leave the user field blank. 38 | 3. Specify the name of this service, ``{{ cookiecutter.project_name }}``, as the client name. 39 | 4. Set the :guilabel:`URL` to the root path of this service: ``http://127.0.0.1:{{cookiecutter.port}}/``. 40 | 5. Set the :guilabel:`Redirect URL` to the complete endpoint: ``http://127.0.0.1:{{cookiecutter.port}}/complete/edx-oauth2/``. 41 | 6. Copy the :guilabel:`Client ID` and :guilabel:`Client Secret` values. They will be used later. 42 | 7. Select :guilabel:`Confidential` as the client type. 43 | 8. Select :guilabel:`Authorization code` as the authorization grant type. 44 | 9. Click :guilabel:`Save`. 45 | 46 | 47 | 48 | Now that you have the client credentials, you can update your settings (ideally in 49 | :file:`{{cookiecutter.repo_name}}/settings/local.py`). The table below describes the relevant settings. 50 | 51 | +-----------------------------------+----------------------------------+--------------------------------------------------------------------------+ 52 | | Setting | Description | Value | 53 | +===================================+==================================+==========================================================================+ 54 | | SOCIAL_AUTH_EDX_OAUTH2_KEY | SSO OAuth 2.0 client key | (This should be set to the value generated when the client was created.) | 55 | +-----------------------------------+----------------------------------+--------------------------------------------------------------------------+ 56 | | SOCIAL_AUTH_EDX_OAUTH2_SECRET | SSO OAuth 2.0 client secret | (This should be set to the value generated when the client was created.) | 57 | +-----------------------------------+----------------------------------+--------------------------------------------------------------------------+ 58 | | SOCIAL_AUTH_EDX_OAUTH2_URL_ROOT | OAuth 2.0 authentication URL | http://127.0.0.1:18000/oauth2 | 59 | +-----------------------------------+----------------------------------+--------------------------------------------------------------------------+ 60 | | BACKEND_SERVICE_EDX_OAUTH2_KEY | IDA<->IDA OAuth 2.0 client key | (This should be set to the value generated when the client was created.) | 61 | +-----------------------------------+----------------------------------+--------------------------------------------------------------------------+ 62 | | BACKEND_SERVICE_EDX_OAUTH2_SECRET | IDA<->IDA OAuth 2.0 client secret| (This should be set to the value generated when the client was created.) | 63 | +-----------------------------------+----------------------------------+--------------------------------------------------------------------------+ 64 | 65 | 66 | Run migrations 67 | -------------- 68 | Local installations use SQLite by default. If you choose to use another database backend, make sure you have updated 69 | your settings and created the database (if necessary). Migrations can be run with `Django's migrate command`_. 70 | 71 | .. code-block:: bash 72 | 73 | $ python manage.py migrate 74 | 75 | .. _Django's migrate command: https://docs.djangoproject.com/en/1.11/ref/django-admin/#django-admin-migrate 76 | 77 | 78 | Run the server 79 | -------------- 80 | The server can be run with `Django's runserver command`_. If you opt to run on a different port, make sure you update 81 | OAuth2 client via LMS admin. 82 | 83 | .. code-block:: bash 84 | 85 | $ python manage.py runserver 8003 86 | 87 | .. _Django's runserver command: https://docs.djangoproject.com/en/1.11/ref/django-admin/#runserver-port-or-address-port 88 | 89 | 90 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | This repository has been archived and is no longer supported—use it at your own risk. This repository may depend on out-of-date libraries with security issues, and security updates will not be provided. Pull requests against this repository will also not be merged. 2 | 3 | This cookiecutter has been moved to `edx-cookiecutters`_ 4 | 5 | .. _edx-cookecutters: https://github.com/edx/edx-cookiecutters 6 | 7 | 8 | 9 | cookiecutter-django-ida |Travis|_ 10 | ================================== 11 | .. |Travis| image:: https://travis-ci.org/edx/cookiecutter-django-ida.svg?branch=master 12 | .. _Travis: https://travis-ci.org/edx/cookiecutter-django-ida 13 | 14 | A cookiecutter_ template for edX Django projects. 15 | 16 | .. _cookiecutter: http://cookiecutter.readthedocs.org/en/latest/index.html 17 | 18 | **This template produces a Python 3.6 project.** 19 | 20 | This cookiecutter template is intended for new edX independently deployable apps (IDAs). It includes the following packages: 21 | 22 | * Django 1.11.x 23 | * Django REST Framework 24 | * Django Waffle 25 | 26 | The necessary configuration is also in place to support: 27 | 28 | * i18n 29 | * Documentation (using Sphinx_) 30 | * Authentication with OAuth2 31 | * Loading settings from YAML (for production) 32 | * Pylint/Pycodestyle validation 33 | * Travis CI 34 | 35 | .. _Sphinx: http://sphinx-doc.org/ 36 | 37 | Usage 38 | ----- 39 | 40 | As with any new project, you will need to create a virtual environment. Once this is set up, install cookiecutter and edx-lint: 41 | 42 | .. code-block:: bash 43 | 44 | $ pip install cookiecutter 45 | $ pip install edx-lint 46 | 47 | cookiecutter has the ability to pull templates directly from git, so there is no need to clone this repo. To access the template, provide the repo path as an argument: 48 | 49 | .. code-block:: bash 50 | 51 | $ cd 52 | $ cookiecutter https://github.com/edx/cookiecutter-django-ida.git 53 | 54 | You will be prompted for a few basic details (described below). These will be used to create the new project. 55 | 56 | .. list-table:: 57 | :widths: 25 75 58 | :header-rows: 1 59 | 60 | * - Variable 61 | - Description 62 | * - project_name 63 | - Full name of the project. (e.g., E-Commerce Service) 64 | * - repo_name 65 | - Short (Python-friendly) name of the project. This should also be the name of the repository (e.g., ecommerce, credentials). 66 | * - repo_port 67 | - Port number for the project. Should be in the form `18***` with the 3 digits being any that aren't currently in use by other services. 68 | * - author_name 69 | - The author of the documentation. Leave this as the default ("edX") unless you have a good reason to change it. 70 | * - description 71 | - A short description of the project, used to initialize the documentation. 72 | 73 | After the new folder is created, you will need to: 74 | 75 | 1. ``cd `` 76 | 2. Create a python 3 virtual environment and activate it 77 | 3. ``make upgrade`` 78 | 4. ``make docker_build`` 79 | 5. ``docker-compose up`` 80 | 81 | **Note** This cookiecutter repo currently has some issues with repos that use a hyphen in their name. If this is the case, some pieces of the repo will need to be changed from ``new-repo-name`` to ``new_repo_name``, particularly the Python pieces. 82 | 83 | Requirements 84 | ~~~~~~~~~~~~ 85 | 86 | Once you initialize your project, run ``make upgrade`` to generate 87 | ``.txt`` files in the ``requirements/`` directory, 88 | which will contain pinned dependency versions. 89 | Regularly re-run this command going forward in order to freshen the version pins. 90 | Failure to do so could open your IDA to bugs, security vulnerabilities, 91 | and other issues. 92 | 93 | It is recommended to follow the instructions in 94 | `Adding repositories for auto python dependency management `_ 95 | in order to perform this process on a regular cadence. 96 | 97 | User Model Customization 98 | ~~~~~~~~~~~~~~~~~~~~~~~~ 99 | 100 | The project includes a custom Django user model in ``core/models.py``. You must further customize this model as your IDA/service requires. You MUST generate migrations for this model before Django can start: 101 | 102 | .. code-block:: bash 103 | 104 | $ python manage.py makemigrations 105 | 106 | Documentation 107 | ~~~~~~~~~~~~~ 108 | 109 | Sphinx is set up for your project in the ``docs`` directory. All developer documentation should be added here (as opposed to a long README file). Doing this also has the added benefit of giving you a good starting point when the time comes to open source your project! 110 | 111 | Sphinx is themed with the common edX theme `edx-sphinx-theme `_ by default. If you wish to publish your documentation to Read the Docs you will need to make sure to take the steps `outlined here `_. 112 | 113 | How To Contribute 114 | ----------------- 115 | 116 | Contributions are welcome. Please read `How To Contribute `_ for details. Even though it was written with ``edx-platform`` in mind, these guidelines should be followed for Open edX code in general. 117 | 118 | Testing 119 | ~~~~~~~ 120 | 121 | The ``Makefile`` includes a ``test`` target that runs basic validation on this template. This validation includes:: 122 | 123 | * Create a new project using the template. 124 | * Generate and install pinned requirements 125 | * Run the project's migrations and validations. 126 | * Extract and compile translations. 127 | * Compile documentation. 128 | 129 | Run this validation using the command below. 130 | 131 | .. code-block:: bash 132 | 133 | $ make test 134 | 135 | Reporting Security Issues 136 | ------------------------- 137 | 138 | Please do not report security issues in public. Please email security@edx.org. 139 | 140 | Get Help 141 | -------- 142 | 143 | Ask questions and discuss this project on `Slack `_ or in the `edx-code Google Group `_. 144 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | 3 | .PHONY: help clean piptools requirements ci_requirements dev_requirements \ 4 | validation_requirements doc_requirementsprod_requirements static shell \ 5 | test coverage isort_check isort style lint quality pii_check validate \ 6 | migrate html_coverage upgrade extract_translation dummy_translations \ 7 | compile_translations fake_translations pull_translations \ 8 | push_translations start-devstack open-devstack pkg-devstack \ 9 | detect_changed_source_translations validate_translations check_keywords 10 | 11 | define BROWSER_PYSCRIPT 12 | import os, webbrowser, sys 13 | try: 14 | from urllib import pathname2url 15 | except: 16 | from urllib.request import pathname2url 17 | 18 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 19 | endef 20 | export BROWSER_PYSCRIPT 21 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 22 | 23 | # Generates a help message. Borrowed from https://github.com/pydanny/cookiecutter-djangopackage. 24 | help: ## display this help message 25 | @echo "Please use \`make \` where is one of" 26 | @awk -F ':.*?## ' '/^[a-zA-Z]/ && NF==2 {printf "\033[36m %-25s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort 27 | 28 | clean: ## delete generated byte code and coverage reports 29 | find . -name '*.pyc' -delete 30 | coverage erase 31 | rm -rf assets 32 | rm -rf pii_report 33 | 34 | piptools: ## install pinned version of pip-compile and pip-sync 35 | pip install -r requirements/pip-tools.txt 36 | 37 | requirements: piptools dev_requirements ## sync to default requirements 38 | 39 | ci_requirements: validation_requirements ## sync to requirements needed for CI checks 40 | 41 | dev_requirements: ## sync to requirements for local development 42 | pip-sync -q requirements/dev.txt 43 | 44 | validation_requirements: ## sync to requirements for testing & code quality checking 45 | pip-sync -q requirements/validation.txt 46 | 47 | doc_requirements: 48 | pip-sync -q requirements/doc.txt 49 | 50 | prod_requirements: ## install requirements for production 51 | pip-sync -q requirements/production.txt 52 | 53 | static: ## generate static files 54 | python manage.py collectstatic --noinput 55 | 56 | shell: ## run Django shell 57 | python manage.py shell 58 | 59 | test: clean ## run tests and generate coverage report 60 | pytest 61 | 62 | # To be run from CI context 63 | coverage: clean 64 | pytest --cov-report html 65 | $(BROWSER) htmlcov/index.html 66 | 67 | isort_check: ## check that isort has been run 68 | isort --check-only -rc {{cookiecutter.repo_name}}/ 69 | 70 | isort: ## run isort to sort imports in all Python files 71 | isort --recursive --atomic {{cookiecutter.repo_name}}/ 72 | 73 | style: ## run Python style checker 74 | pylint --rcfile=pylintrc {{cookiecutter.repo_name}} *.py 75 | 76 | lint: ## run Python code linting 77 | pylint --rcfile=pylintrc {{cookiecutter.repo_name}} *.py 78 | 79 | quality: style isort_check lint ## check code style and import sorting, then lint 80 | 81 | pii_check: ## check for PII annotations on all Django models 82 | DJANGO_SETTINGS_MODULE={{cookiecutter.repo_name}}.settings.test \ 83 | code_annotations django_find_annotations --config_file .pii_annotations.yml --lint --report --coverage 84 | 85 | check_keywords: ## Scan the Django models in all installed apps in this project for restricted field names 86 | python manage.py check_reserved_keywords --override_file db_keyword_overrides.yml 87 | 88 | validate: test quality pii_check check_keywords ## run tests, quality, and PII annotation checks 89 | 90 | migrate: ## apply database migrations 91 | python manage.py migrate 92 | 93 | html_coverage: ## generate and view HTML coverage report 94 | coverage html && open htmlcov/index.html 95 | 96 | upgrade: export CUSTOM_COMPILE_COMMAND=make upgrade 97 | upgrade: piptools ## update the requirements/*.txt files with the latest packages satisfying requirements/*.in 98 | # Make sure to compile files after any other files they include! 99 | pip-compile --upgrade -o requirements/pip-tools.txt requirements/pip-tools.in 100 | pip-compile --upgrade -o requirements/base.txt requirements/base.in 101 | pip-compile --upgrade -o requirements/test.txt requirements/test.in 102 | pip-compile --upgrade -o requirements/doc.txt requirements/doc.in 103 | pip-compile --upgrade -o requirements/quality.txt requirements/quality.in 104 | pip-compile --upgrade -o requirements/validation.txt requirements/validation.in 105 | pip-compile --upgrade -o requirements/dev.txt requirements/dev.in 106 | pip-compile --upgrade -o requirements/production.txt requirements/production.in 107 | 108 | extract_translations: ## extract strings to be translated, outputting .mo files 109 | python manage.py makemessages -l en -v1 -d django 110 | python manage.py makemessages -l en -v1 -d djangojs 111 | 112 | dummy_translations: ## generate dummy translation (.po) files 113 | cd {{cookiecutter.repo_name}} && i18n_tool dummy 114 | 115 | compile_translations: # compile translation files, outputting .po files for each supported language 116 | python manage.py compilemessages 117 | 118 | fake_translations: ## generate and compile dummy translation files 119 | 120 | pull_translations: ## pull translations from Transifex 121 | tx pull -af --mode reviewed 122 | 123 | push_translations: ## push source translation files (.po) from Transifex 124 | tx push -s 125 | 126 | start-devstack: ## run a local development copy of the server 127 | docker-compose --x-networking up 128 | 129 | open-devstack: ## open a shell on the server started by start-devstack 130 | docker exec -it {{cookiecutter.repo_name}} /edx/app/{{cookiecutter.repo_name}}/devstack.sh open 131 | 132 | pkg-devstack: ## build the {{cookiecutter.repo_name}} image from the latest configuration and code 133 | docker build -t {{cookiecutter.repo_name}}:latest -f docker/build/{{cookiecutter.repo_name}}/Dockerfile git://github.com/edx/configuration 134 | 135 | detect_changed_source_translations: ## check if translation files are up-to-date 136 | cd {{cookiecutter.repo_name}} && i18n_tool changed 137 | 138 | validate_translations: fake_translations detect_changed_source_translations ## install fake translations and check if translation files are up-to-date 139 | 140 | docker_build: 141 | docker build . -f Dockerfile -t openedx/{{cookiecutter.repo_name}} 142 | docker build . -f Dockerfile --target newrelic -t openedx/{{cookiecutter.repo_name}}:latest-newrelic 143 | 144 | travis_docker_tag: docker_build 145 | docker tag openedx/{{cookiecutter.repo_name}} openedx/{{cookiecutter.repo_name}}:$$TRAVIS_COMMIT 146 | docker tag openedx/{{cookiecutter.repo_name}}:latest-newrelic openedx/{{cookiecutter.repo_name}}:$$TRAVIS_COMMIT-newrelic 147 | 148 | travis_docker_auth: 149 | echo "$$DOCKER_PASSWORD" | docker login -u "$$DOCKER_USERNAME" --password-stdin 150 | 151 | travis_docker_push: travis_docker_tag travis_docker_auth ## push to docker hub 152 | docker push 'openedx/{{cookiecutter.repo_name}}:latest' 153 | docker push "openedx/{{cookiecutter.repo_name}}:$$TRAVIS_COMMIT" 154 | docker push 'openedx/{{cookiecutter.repo_name}}:latest-newrelic' 155 | docker push "openedx/{{cookiecutter.repo_name}}:$$TRAVIS_COMMIT-newrelic" 156 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/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 clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 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 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/{{cookiecutter.repo_name}}.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/{{cookiecutter.repo_name}}.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/{{cookiecutter.repo_name}}" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/{{cookiecutter.repo_name}}" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/{{cookiecutter.repo_name}}/settings/base.py: -------------------------------------------------------------------------------- 1 | import os 2 | from os.path import abspath, dirname, join 3 | 4 | from corsheaders.defaults import default_headers as corsheaders_default_headers 5 | 6 | from {{cookiecutter.repo_name}}.settings.utils import get_logger_config 7 | 8 | # PATH vars 9 | here = lambda *x: join(abspath(dirname(__file__)), *x) 10 | PROJECT_ROOT = here("..") 11 | root = lambda *x: join(abspath(PROJECT_ROOT), *x) 12 | 13 | 14 | # SECURITY WARNING: keep the secret key used in production secret! 15 | SECRET_KEY = os.environ.get('{{cookiecutter.repo_name|upper}}_SECRET_KEY', 'insecure-secret-key') 16 | 17 | # SECURITY WARNING: don't run with debug turned on in production! 18 | DEBUG = False 19 | 20 | ALLOWED_HOSTS = [] 21 | 22 | # Application definition 23 | 24 | INSTALLED_APPS = ( 25 | 'django.contrib.admin', 26 | 'django.contrib.auth', 27 | 'django.contrib.contenttypes', 28 | 'django.contrib.sessions', 29 | 'django.contrib.messages', 30 | 'django.contrib.staticfiles', 31 | 'release_util', 32 | ) 33 | 34 | THIRD_PARTY_APPS = ( 35 | 'corsheaders', 36 | 'csrf.apps.CsrfAppConfig', # Enables frontend apps to retrieve CSRF tokens 37 | 'rest_framework', 38 | 'rest_framework_swagger', 39 | 'social_django', 40 | 'waffle', 41 | ) 42 | 43 | PROJECT_APPS = ( 44 | '{{cookiecutter.repo_name}}.apps.core', 45 | '{{cookiecutter.repo_name}}.apps.api', 46 | ) 47 | 48 | INSTALLED_APPS += THIRD_PARTY_APPS 49 | INSTALLED_APPS += PROJECT_APPS 50 | 51 | MIDDLEWARE = ( 52 | # Resets RequestCache utility for added safety. 53 | 'edx_django_utils.cache.middleware.RequestCacheMiddleware', 54 | # Enables monitoring utility for writing custom metrics. 55 | 'edx_django_utils.monitoring.middleware.MonitoringCustomMetricsMiddleware', 56 | 'corsheaders.middleware.CorsMiddleware', 57 | 'django.contrib.sessions.middleware.SessionMiddleware', 58 | 'django.middleware.locale.LocaleMiddleware', 59 | 'django.middleware.common.CommonMiddleware', 60 | 'edx_rest_framework_extensions.auth.jwt.middleware.JwtAuthCookieMiddleware', 61 | 'django.middleware.csrf.CsrfViewMiddleware', 62 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 63 | 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 64 | 'django.contrib.messages.middleware.MessageMiddleware', 65 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 66 | 'social_django.middleware.SocialAuthExceptionMiddleware', 67 | 'waffle.middleware.WaffleMiddleware', 68 | # Enables force_django_cache_miss functionality for TieredCache. 69 | 'edx_django_utils.cache.middleware.TieredCacheMiddleware', 70 | # Outputs monitoring metrics for a request. 71 | 'edx_rest_framework_extensions.middleware.RequestMetricsMiddleware', 72 | # Ensures proper DRF permissions in support of JWTs 73 | 'edx_rest_framework_extensions.auth.jwt.middleware.EnsureJWTAuthSettingsMiddleware', 74 | ) 75 | 76 | # Enable CORS 77 | CORS_ALLOW_CREDENTIALS = True 78 | CORS_ALLOW_HEADERS = corsheaders_default_headers + ( 79 | 'use-jwt-cookie', 80 | ) 81 | CORS_ORIGIN_WHITELIST = [] 82 | 83 | ROOT_URLCONF = '{{cookiecutter.repo_name}}.urls' 84 | 85 | # Python dotted path to the WSGI application used by Django's runserver. 86 | WSGI_APPLICATION = '{{cookiecutter.repo_name}}.wsgi.application' 87 | 88 | # Database 89 | # https://docs.djangoproject.com/en/1.11/ref/settings/#databases 90 | # Set this value in the environment-specific files (e.g. local.py, production.py, test.py) 91 | DATABASES = { 92 | 'default': { 93 | 'ENGINE': 'django.db.backends.', 94 | 'NAME': '', 95 | 'USER': '', 96 | 'PASSWORD': '', 97 | 'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP. 98 | 'PORT': '', # Set to empty string for default. 99 | } 100 | } 101 | 102 | # Internationalization 103 | # https://docs.djangoproject.com/en/dev/topics/i18n/ 104 | 105 | LANGUAGE_CODE = 'en-us' 106 | 107 | TIME_ZONE = 'UTC' 108 | 109 | USE_I18N = True 110 | 111 | USE_L10N = True 112 | 113 | USE_TZ = True 114 | 115 | LOCALE_PATHS = ( 116 | root('conf', 'locale'), 117 | ) 118 | 119 | 120 | # MEDIA CONFIGURATION 121 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root 122 | MEDIA_ROOT = root('media') 123 | 124 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url 125 | MEDIA_URL = '/media/' 126 | # END MEDIA CONFIGURATION 127 | 128 | 129 | # STATIC FILE CONFIGURATION 130 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root 131 | STATIC_ROOT = root('assets') 132 | 133 | # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url 134 | STATIC_URL = '/static/' 135 | 136 | # See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS 137 | STATICFILES_DIRS = ( 138 | root('static'), 139 | ) 140 | 141 | # TEMPLATE CONFIGURATION 142 | # See: https://docs.djangoproject.com/en/1.11/ref/settings/#templates 143 | TEMPLATES = [ 144 | { 145 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 146 | 'APP_DIRS': True, 147 | 'DIRS': ( 148 | root('templates'), 149 | ), 150 | 'OPTIONS': { 151 | 'context_processors': ( 152 | 'django.contrib.auth.context_processors.auth', 153 | 'django.template.context_processors.debug', 154 | 'django.template.context_processors.i18n', 155 | 'django.template.context_processors.media', 156 | 'django.template.context_processors.static', 157 | 'django.template.context_processors.tz', 158 | 'django.contrib.messages.context_processors.messages', 159 | '{{cookiecutter.repo_name}}.apps.core.context_processors.core', 160 | ), 161 | 'debug': True, # Django will only display debug pages if the global DEBUG setting is set to True. 162 | } 163 | }, 164 | ] 165 | # END TEMPLATE CONFIGURATION 166 | 167 | 168 | # COOKIE CONFIGURATION 169 | # The purpose of customizing the cookie names is to avoid conflicts when 170 | # multiple Django services are running behind the same hostname. 171 | # Detailed information at: https://docs.djangoproject.com/en/dev/ref/settings/ 172 | SESSION_COOKIE_NAME = '{{cookiecutter.repo_name}}_sessionid' 173 | CSRF_COOKIE_NAME = '{{cookiecutter.repo_name}}_csrftoken' 174 | LANGUAGE_COOKIE_NAME = '{{cookiecutter.repo_name}}_language' 175 | # END COOKIE CONFIGURATION 176 | 177 | CSRF_COOKIE_SECURE = False 178 | CSRF_TRUSTED_ORIGINS = [] 179 | 180 | # AUTHENTICATION CONFIGURATION 181 | LOGIN_URL = '/login/' 182 | LOGOUT_URL = '/logout/' 183 | 184 | AUTH_USER_MODEL = 'core.User' 185 | 186 | AUTHENTICATION_BACKENDS = ( 187 | 'auth_backends.backends.EdXOAuth2', 188 | 'django.contrib.auth.backends.ModelBackend', 189 | ) 190 | 191 | ENABLE_AUTO_AUTH = False 192 | AUTO_AUTH_USERNAME_PREFIX = 'auto_auth_' 193 | 194 | SOCIAL_AUTH_STRATEGY = 'auth_backends.strategies.EdxDjangoStrategy' 195 | 196 | # Set these to the correct values for your OAuth2 provider (e.g., LMS) 197 | SOCIAL_AUTH_EDX_OAUTH2_KEY = 'replace-me' 198 | SOCIAL_AUTH_EDX_OAUTH2_SECRET = 'replace-me' 199 | SOCIAL_AUTH_EDX_OAUTH2_URL_ROOT = 'replace-me' 200 | SOCIAL_AUTH_EDX_OAUTH2_LOGOUT_URL = 'replace-me' 201 | BACKEND_SERVICE_EDX_OAUTH2_KEY = 'replace-me' 202 | BACKEND_SERVICE_EDX_OAUTH2_SECRET = 'replace-me' 203 | 204 | JWT_AUTH = { 205 | 'JWT_ISSUER': 'http://127.0.0.1:8000/oauth2', 206 | 'JWT_ALGORITHM': 'HS256', 207 | 'JWT_VERIFY_EXPIRATION': True, 208 | 'JWT_PAYLOAD_GET_USERNAME_HANDLER': lambda d: d.get('preferred_username'), 209 | 'JWT_LEEWAY': 1, 210 | 'JWT_DECODE_HANDLER': 'edx_rest_framework_extensions.auth.jwt.decoder.jwt_decode_handler', 211 | 'JWT_PUBLIC_SIGNING_JWK_SET': None, 212 | 'JWT_AUTH_COOKIE_HEADER_PAYLOAD': 'edx-jwt-cookie-header-payload', 213 | 'JWT_AUTH_COOKIE_SIGNATURE': 'edx-jwt-cookie-signature', 214 | 'JWT_AUTH_REFRESH_COOKIE': 'edx-jwt-refresh-cookie', 215 | } 216 | 217 | # Request the user's permissions in the ID token 218 | EXTRA_SCOPE = ['permissions'] 219 | 220 | # TODO Set this to another (non-staff, ideally) path. 221 | LOGIN_REDIRECT_URL = '/admin/' 222 | # END AUTHENTICATION CONFIGURATION 223 | 224 | 225 | # OPENEDX-SPECIFIC CONFIGURATION 226 | PLATFORM_NAME = 'Your Platform Name Here' 227 | # END OPENEDX-SPECIFIC CONFIGURATION 228 | 229 | # Set up logging for development use (logging to stdout) 230 | LOGGING = get_logger_config(debug=DEBUG, dev_env=True, local_loglevel='DEBUG') 231 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # {{ cookiecutter.project_name }} documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Feb 17 11:46:20 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import os 15 | import edx_theme 16 | 17 | # If you wish to publish docs to readthedocs.org you'll need to make sure to 18 | # follow the steps here: 19 | # https://edx-sphinx-theme.readthedocs.io/en/latest/readme.html#read-the-docs-configuration 20 | 21 | html_theme = 'edx_theme' 22 | html_theme_path = [edx_theme.get_html_theme_path()] 23 | 24 | # If extensions (or modules to document with autodoc) are in another directory, 25 | # add these directories to sys.path here. If the directory is relative to the 26 | # documentation root, use os.path.abspath to make it absolute, like shown here. 27 | # sys.path.insert(0, os.path.abspath('.')) 28 | 29 | # -- General configuration ----------------------------------------------------- 30 | 31 | # If your documentation needs a minimal Sphinx version, state it here. 32 | # needs_sphinx = '1.0' 33 | 34 | # Add any Sphinx extension module names here, as strings. They can be extensions 35 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 36 | extensions = ['edx_theme'] 37 | 38 | # Add any paths that contain templates here, relative to this directory. 39 | templates_path = ['_templates'] 40 | 41 | # The suffix of source filenames. 42 | source_suffix = '.rst' 43 | 44 | # The encoding of source files. 45 | # source_encoding = 'utf-8-sig' 46 | 47 | # The master toctree document. 48 | master_doc = 'index' 49 | 50 | # General information about the project. 51 | project = u'{{ cookiecutter.project_name }}' 52 | copyright = edx_theme.COPYRIGHT 53 | author = u'{{ cookiecutter.author_name }}' 54 | 55 | # The version info for the project you're documenting, acts as replacement for 56 | # |version| and |release|, also used in various other places throughout the 57 | # built documents. 58 | # 59 | # The short X.Y version. 60 | version = '0.1' 61 | # The full version, including alpha/beta/rc tags. 62 | release = '0.1' 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | # language = None 67 | 68 | # There are two options for replacing |today|: either, you set today to some 69 | # non-false value, then it is used: 70 | # today = '' 71 | # Else, today_fmt is used as the format for a strftime call. 72 | # today_fmt = '%B %d, %Y' 73 | 74 | # List of patterns, relative to source directory, that match files and 75 | # directories to ignore when looking for source files. 76 | exclude_patterns = ['_build'] 77 | 78 | # The reST default role (used for this markup: `text`) to use for all documents. 79 | # default_role = None 80 | 81 | # If true, '()' will be appended to :func: etc. cross-reference text. 82 | # add_function_parentheses = True 83 | 84 | # If true, the current module name will be prepended to all description 85 | # unit titles (such as .. function::). 86 | # add_module_names = True 87 | 88 | # If true, sectionauthor and moduleauthor directives will be shown in the 89 | # output. They are ignored by default. 90 | # show_authors = False 91 | 92 | # The name of the Pygments (syntax highlighting) style to use. 93 | pygments_style = 'sphinx' 94 | 95 | # A list of ignored prefixes for module index sorting. 96 | # modindex_common_prefix = [] 97 | 98 | 99 | # -- Options for HTML output --------------------------------------------------- 100 | 101 | # The theme to use for HTML and HTML Help pages. See the documentation for 102 | # a list of builtin themes. 103 | # html_theme = 'sphinx_rtd_theme' 104 | 105 | # Theme options are theme-specific and customize the look and feel of a theme 106 | # further. For a list of options available for each theme, see the 107 | # documentation. 108 | # html_theme_options = {} 109 | 110 | # Add any paths that contain custom themes here, relative to this directory. 111 | # html_theme_path = [] 112 | 113 | # The name for this set of Sphinx documents. If None, it defaults to 114 | # " v documentation". 115 | # html_title = None 116 | 117 | # A shorter title for the navigation bar. Default is the same as html_title. 118 | # html_short_title = None 119 | 120 | # The name of an image file (relative to this directory) to place at the top 121 | # of the sidebar. 122 | # html_logo = None 123 | 124 | # The name of an image file (within the static path) to use as favicon of the 125 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 126 | # pixels large. 127 | html_favicon = os.path.join(html_theme_path[0], 'edx_theme', 'static', 'css', 'favicon.ico') 128 | 129 | # Add any paths that contain custom static files (such as style sheets) here, 130 | # relative to this directory. They are copied after the builtin static files, 131 | # so a file named "default.css" will overwrite the builtin "default.css". 132 | html_static_path = ['_static'] 133 | 134 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 135 | # using the given strftime format. 136 | # html_last_updated_fmt = '%b %d, %Y' 137 | 138 | # If true, SmartyPants will be used to convert quotes and dashes to 139 | # typographically correct entities. 140 | # html_use_smartypants = True 141 | 142 | # Custom sidebar templates, maps document names to template names. 143 | # html_sidebars = {} 144 | 145 | # Additional templates that should be rendered to pages, maps page names to 146 | # template names. 147 | # html_additional_pages = {} 148 | 149 | # If false, no module index is generated. 150 | # html_domain_indices = True 151 | 152 | # If false, no index is generated. 153 | # html_use_index = True 154 | 155 | # If true, the index is split into individual pages for each letter. 156 | # html_split_index = False 157 | 158 | # If true, links to the reST sources are added to the pages. 159 | # html_show_sourcelink = True 160 | 161 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 162 | # html_show_sphinx = True 163 | 164 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 165 | # html_show_copyright = True 166 | 167 | # If true, an OpenSearch description file will be output, and all pages will 168 | # contain a tag referring to it. The value of this option must be the 169 | # base URL from which the finished HTML is served. 170 | # html_use_opensearch = '' 171 | 172 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 173 | # html_file_suffix = None 174 | 175 | # Output file base name for HTML help builder. 176 | htmlhelp_basename = '{{ cookiecutter.repo_name }}doc' 177 | 178 | 179 | # -- Options for LaTeX output -------------------------------------------------- 180 | 181 | latex_elements = { 182 | # The paper size ('letterpaper' or 'a4paper'). 183 | # 'papersize': 'letterpaper', 184 | 185 | # The font size ('10pt', '11pt' or '12pt'). 186 | # 'pointsize': '10pt', 187 | 188 | # Additional stuff for the LaTeX preamble. 189 | # 'preamble': '', 190 | } 191 | 192 | # Grouping the document tree into LaTeX files. List of tuples 193 | # (source start file, target name, title, author, documentclass [howto/manual]). 194 | latex_documents = [ 195 | ('index', '{{ cookiecutter.repo_name }}.tex', u'{{ cookiecutter.project_name }} Documentation', 196 | u'{{ cookiecutter.author_name }}', 'manual'), 197 | ] 198 | 199 | # The name of an image file (relative to this directory) to place at the top of 200 | # the title page. 201 | # latex_logo = None 202 | 203 | # For "manual" documents, if this is true, then toplevel headings are parts, 204 | # not chapters. 205 | # latex_use_parts = False 206 | 207 | # If true, show page references after internal links. 208 | # latex_show_pagerefs = False 209 | 210 | # If true, show URL addresses after external links. 211 | # latex_show_urls = False 212 | 213 | # Documents to append as an appendix to all manuals. 214 | # latex_appendices = [] 215 | 216 | # If false, no module index is generated. 217 | # latex_domain_indices = True 218 | 219 | 220 | # -- Options for manual page output -------------------------------------------- 221 | 222 | # One entry per manual page. List of tuples 223 | # (source start file, name, description, authors, manual section). 224 | man_pages = [ 225 | ('index', '{{ cookiecutter.project_name }}', u'{{ cookiecutter.project_name }} Documentation', 226 | [u'{{ cookiecutter.author_name }}'], 1) 227 | ] 228 | 229 | # If true, show URL addresses after external links. 230 | # man_show_urls = False 231 | 232 | 233 | # -- Options for Texinfo output ------------------------------------------------ 234 | 235 | # Grouping the document tree into Texinfo files. List of tuples 236 | # (source start file, target name, title, author, 237 | # dir menu entry, description, category) 238 | texinfo_documents = [ 239 | ('index', '{{ cookiecutter.project_name }}', u'{{ cookiecutter.project_name }} Documentation', 240 | u'{{ cookiecutter.author_name }}', '{{ cookiecutter.project_name }}', '{{ cookiecutter.project_name }}', 241 | 'Miscellaneous' 242 | ), 243 | ] 244 | 245 | 246 | # Documents to append as an appendix to all manuals. 247 | # texinfo_appendices = [] 248 | 249 | # If false, no module index is generated. 250 | # texinfo_domain_indices = True 251 | 252 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 253 | # texinfo_show_urls = 'footnote' 254 | 255 | 256 | def setup(app): 257 | app.add_stylesheet('theme_overrides.css') 258 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /{{cookiecutter.repo_name}}/LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published by 637 | the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | 663 | EdX Inc. wishes to state, in clarification of the above license terms, that 664 | any public, independently available web service offered over the network and 665 | communicating with edX's copyrighted works by any form of inter-service 666 | communication, including but not limited to Remote Procedure Call (RPC) 667 | interfaces, is not a work based on our copyrighted work within the meaning 668 | of the license. "Corresponding Source" of this work, or works based on this 669 | work, as defined by the terms of this license do not include source code 670 | files for programs used solely to provide those public, independently 671 | available web services. 672 | --------------------------------------------------------------------------------