From 3087097e7f2a30d2754eab51b08396f708b675e0 Mon Sep 17 00:00:00 2001 From: Brandon Baker Date: Tue, 7 Apr 2020 12:33:41 -0400 Subject: [PATCH] License-manager setup from django cookiecutter --- .annotation_safe_list.yml | 37 + .bowerrc | 4 + .coveragerc | 12 + .editorconfig | 94 +++ .github/PULL_REQUEST_TEMPLATE.md | 54 ++ .gitignore | 86 +++ .pii_annotations.yml | 35 + .travis.yml | 17 + .tx/config | 14 + CONTRIBUTING.md | 7 + LICENSE.txt | 671 ++++++++++++++++++ Makefile | 135 ++++ README.rst | 38 + codecov.yml | 9 + docker-compose.yml | 25 + docs/Makefile | 192 +++++ docs/__init__.py | 1 + docs/_static/theme_overrides.css | 10 + docs/conf.py | 257 +++++++ docs/features.rst | 28 + docs/getting_started.rst | 117 +++ docs/index.rst | 16 + docs/internationalization.rst | 48 ++ docs/testing.rst | 14 + license_manager/__init__.py | 0 license_manager/apps/__init__.py | 0 license_manager/apps/api/__init__.py | 0 license_manager/apps/api/models.py | 4 + license_manager/apps/api/serializers.py | 4 + license_manager/apps/api/tests/__init__.py | 1 + license_manager/apps/api/urls.py | 15 + license_manager/apps/api/v1/__init__.py | 0 license_manager/apps/api/v1/tests/__init__.py | 1 + license_manager/apps/api/v1/urls.py | 4 + license_manager/apps/api/v1/views.py | 1 + license_manager/apps/core/__init__.py | 0 license_manager/apps/core/admin.py | 22 + license_manager/apps/core/constants.py | 7 + .../apps/core/context_processors.py | 9 + .../apps/core/migrations/__init__.py | 0 license_manager/apps/core/models.py | 39 + license_manager/apps/core/tests/__init__.py | 0 .../core/tests/test_context_processors.py | 17 + .../apps/core/tests/test_models.py | 45 ++ license_manager/apps/core/tests/test_views.py | 76 ++ license_manager/apps/core/views.py | 86 +++ license_manager/conf/locale/config.yaml | 85 +++ license_manager/settings/__init__.py | 0 license_manager/settings/base.py | 271 +++++++ license_manager/settings/devstack.py | 24 + license_manager/settings/local.py | 68 ++ license_manager/settings/private.py.example | 5 + license_manager/settings/production.py | 55 ++ license_manager/settings/test.py | 17 + license_manager/settings/utils.py | 12 + license_manager/static/.keep | 0 license_manager/templates/.keep | 0 license_manager/urls.py | 44 ++ license_manager/wsgi.py | 22 + manage.py | 15 + openedx.yaml | 13 + pylintrc | 456 ++++++++++++ pylintrc_tweaks | 8 + pytest.ini | 10 + requirements.txt | 3 + requirements/base.in | 13 + requirements/base.txt | 56 ++ requirements/dev.in | 9 + requirements/doc.in | 8 + requirements/doc.txt | 110 +++ requirements/monitoring/requirements.txt | 9 + requirements/optional.txt | 1 + requirements/pip-tools.in | 3 + requirements/pip-tools.txt | 9 + requirements/private.readme | 15 + requirements/production.in | 7 + requirements/quality.in | 9 + requirements/quality.txt | 81 +++ requirements/test.in | 11 + requirements/test.txt | 88 +++ requirements/validation.in | 4 + requirements/validation.txt | 98 +++ setup.cfg | 14 + 83 files changed, 3905 insertions(+) create mode 100644 .annotation_safe_list.yml create mode 100644 .bowerrc create mode 100644 .coveragerc create mode 100644 .editorconfig create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .gitignore create mode 100644 .pii_annotations.yml create mode 100644 .travis.yml create mode 100644 .tx/config create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE.txt create mode 100644 Makefile create mode 100644 README.rst create mode 100644 codecov.yml create mode 100644 docker-compose.yml create mode 100644 docs/Makefile create mode 100644 docs/__init__.py create mode 100644 docs/_static/theme_overrides.css create mode 100644 docs/conf.py create mode 100644 docs/features.rst create mode 100644 docs/getting_started.rst create mode 100644 docs/index.rst create mode 100644 docs/internationalization.rst create mode 100644 docs/testing.rst create mode 100644 license_manager/__init__.py create mode 100644 license_manager/apps/__init__.py create mode 100644 license_manager/apps/api/__init__.py create mode 100644 license_manager/apps/api/models.py create mode 100644 license_manager/apps/api/serializers.py create mode 100644 license_manager/apps/api/tests/__init__.py create mode 100644 license_manager/apps/api/urls.py create mode 100644 license_manager/apps/api/v1/__init__.py create mode 100644 license_manager/apps/api/v1/tests/__init__.py create mode 100644 license_manager/apps/api/v1/urls.py create mode 100644 license_manager/apps/api/v1/views.py create mode 100644 license_manager/apps/core/__init__.py create mode 100644 license_manager/apps/core/admin.py create mode 100644 license_manager/apps/core/constants.py create mode 100644 license_manager/apps/core/context_processors.py create mode 100644 license_manager/apps/core/migrations/__init__.py create mode 100644 license_manager/apps/core/models.py create mode 100644 license_manager/apps/core/tests/__init__.py create mode 100644 license_manager/apps/core/tests/test_context_processors.py create mode 100644 license_manager/apps/core/tests/test_models.py create mode 100644 license_manager/apps/core/tests/test_views.py create mode 100644 license_manager/apps/core/views.py create mode 100644 license_manager/conf/locale/config.yaml create mode 100644 license_manager/settings/__init__.py create mode 100644 license_manager/settings/base.py create mode 100644 license_manager/settings/devstack.py create mode 100644 license_manager/settings/local.py create mode 100644 license_manager/settings/private.py.example create mode 100644 license_manager/settings/production.py create mode 100644 license_manager/settings/test.py create mode 100644 license_manager/settings/utils.py create mode 100644 license_manager/static/.keep create mode 100644 license_manager/templates/.keep create mode 100644 license_manager/urls.py create mode 100644 license_manager/wsgi.py create mode 100755 manage.py create mode 100644 openedx.yaml create mode 100644 pylintrc create mode 100644 pylintrc_tweaks create mode 100644 pytest.ini create mode 100644 requirements.txt create mode 100644 requirements/base.in create mode 100644 requirements/base.txt create mode 100644 requirements/dev.in create mode 100644 requirements/doc.in create mode 100644 requirements/doc.txt create mode 100644 requirements/monitoring/requirements.txt create mode 100644 requirements/optional.txt create mode 100644 requirements/pip-tools.in create mode 100644 requirements/pip-tools.txt create mode 100644 requirements/private.readme create mode 100644 requirements/production.in create mode 100644 requirements/quality.in create mode 100644 requirements/quality.txt create mode 100644 requirements/test.in create mode 100644 requirements/test.txt create mode 100644 requirements/validation.in create mode 100644 requirements/validation.txt create mode 100644 setup.cfg diff --git a/.annotation_safe_list.yml b/.annotation_safe_list.yml new file mode 100644 index 00000000..45c6a18f --- /dev/null +++ b/.annotation_safe_list.yml @@ -0,0 +1,37 @@ +# This is a Code Annotations automatically-generated Django model safelist file. +# These models must be annotated as follows in order to be counted in the coverage report. +# See https://code-annotations.readthedocs.io/en/latest/safelist.html for more information. +# +# fake_app_1.FakeModelName: +# ".. no_pii:": "This model has no PII" +# fake_app_2.FakeModel2: +# ".. choice_annotation:": foo, bar, baz + +admin.LogEntry: + ".. no_pii:": "This model has no PII" +auth.Group: + ".. no_pii:": "This model has no PII" +auth.Permission: + ".. no_pii:": "This model has no PII" +contenttypes.ContentType: + ".. no_pii:": "This model has no PII" +sessions.Session: + ".. no_pii:": "This model has no PII" +social_django.Association: + ".. no_pii:": "This model has no PII" +social_django.Code: + ".. pii:": "Email address" + ".. pii_types:": other + ".. pii_retirement:": local_api +social_django.Nonce: + ".. no_pii:": "This model has no PII" +social_django.Partial: + ".. no_pii:": "This model has no PII" +social_django.UserSocialAuth: + ".. no_pii:": "This model has no PII" +waffle.Flag: + ".. no_pii:": "This model has no PII" +waffle.Sample: + ".. no_pii:": "This model has no PII" +waffle.Switch: + ".. no_pii:": "This model has no PII" diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 00000000..e1cf0b9d --- /dev/null +++ b/.bowerrc @@ -0,0 +1,4 @@ +{ + "directory": "license_manager/static/bower_components", + "interactive": false +} diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..b706ca23 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,12 @@ +[run] +branch = True +data_file = .coverage +source=license_manager +omit = + license_manager/settings* + license_manager/conf* + *wsgi.py + *migrations* + *admin.py + *static* + *templates* diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..94d3e718 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,94 @@ +# *************************** +# ** DO NOT EDIT THIS FILE ** +# *************************** +# +# This file was generated by edx-lint: http://github.com/edx/edx-lint +# +# If you want to change this file, you have two choices, depending on whether +# you want to make a local change that applies only to this repo, or whether +# you want to make a central change that applies to all repos using edx-lint. +# +# LOCAL CHANGE: +# +# 1. Edit the local .editorconfig_tweaks file to add changes just to this +# repo's file. +# +# 2. Run: +# +# $ edx_lint write .editorconfig +# +# 3. This will modify the local file. Submit a pull request to get it +# checked in so that others will benefit. +# +# +# CENTRAL CHANGE: +# +# 1. Edit the .editorconfig file in the edx-lint repo at +# https://github.com/edx/edx-lint/blob/master/edx_lint/files/.editorconfig +# +# 2. install the updated version of edx-lint (in edx-lint): +# +# $ pip install . +# +# 3. Run (in edx-lint): +# +# # uses .editorconfig_tweaks from edx-lint for linting in edx-lint +# # NOTE: Use Python 3.x, which no longer includes comments in the output file +# $ edx_lint write .editorconfig +# +# 4. Make a new version of edx_lint, submit and review a pull request with the +# .editorconfig update, and after merging, update the edx-lint version by +# creating a new tag in the repo (uses pbr). +# +# 5. In your local repo, install the newer version of edx-lint. +# +# 6. Run: +# +# # uses local .editorconfig_tweaks +# $ edx_lint write .editorconfig +# +# 7. This will modify the local file. Submit a pull request to get it +# checked in so that others will benefit. +# +# +# +# +# +# STAY AWAY FROM THIS FILE! +# +# +# +# +# +# SERIOUSLY. +# +# ------------------------------ +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 4 +max_line_length = 120 +trim_trailing_whitespace = true + +[{Makefile, *.mk}] +indent_style = tab +indent_size = 8 + +[*.{yml,yaml,json}] +indent_size = 2 + +[*.js] +indent_size = 2 + +[*.diff] +trim_trailing_whitespace = false + +[.git/*] +trim_trailing_whitespace = false + +[*.rst] +max_line_length = 79 + +# 01d24285b953f74272f86b1e42a0235315085e59 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..d250f73f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,54 @@ +## Description + +TODO: Include a detailed description of the changes in the body of the PR + +## Manual Test Plan + +TODO: Include instructions for any required manual tests, and any manual testing that has +already been performed. + +## Testing Checklist +- TODO: Include unit, integration, acceptance tests as appropriate +- TODO: Include accessibility (a11y) tests +- TODO: Include tests that capture the external-query scaling properties +- [ ] Check that Database migrations are backwards-compatible +- [ ] Manually test right-to-left languages and i18n + of the changes. + +## Non-testing Checklist +- TODO: Tag DevOps on your PR (user `edx/devops`), either for review + (more specific to devops = better) or just a general FYI. +- TODO: Consider any documentation your change might need, and which + users will be affected by this change. +- TODO: Double-check that your commit messages will make a meaningful release note. + +## Post-review +- TODO: Squash commits into discrete sets of changes (see the note about release notes above) + +## Reviewers +If you've been tagged for review, please check your corresponding box once you've given the :+1:. +- [ ] Code review #1 (TODO: tag a specific user) +- [ ] Code review #2 (TODO: tag a specific user) +- [ ] Docs review (required for UI strings/error messages) +- [ ] UX review +- [ ] Accessibility review +- [ ] Product review + +### Areas to Consider +- [ ] i18n + - Are all user-facing strings tagged? +- [ ] Right-to-left + - Will the feature work for right-to-left languages? +- [ ] Analytics + - Are any new events being emitted? + - Have any events been changed? + - Are there any new user actions that should be emitted as events? +- [ ] Performance + - What dimensions does this change scale over? Users? Courses? Course size? + - How does the feature scale over those dimensions? Sub-linear? Linear? Quadratic? Exponential? + - How does the scaling affect the number of external calls (database queries, + api requests, etc) that this code makes? +- [ ] Database migrations + - Are they backwards compatible? + - When they run on production, how long will they take? Will they lock the table? + diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..cbe03d05 --- /dev/null +++ b/.gitignore @@ -0,0 +1,86 @@ +*.py[cod] + +*.log + +# C extensions +*.so + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg +lib +lib64 +__pycache__ + +# Installer logs +pip-log.txt + +# Django +default.db + +# Static assets +media/cache/ +assets/ + +# Unit test / coverage reports +.coverage +htmlcov +.tox +nosetests.xml +unittests.xml + +# PII annotation reports +pii_report + +### Internationalization artifacts +*.mo +*.po +*.prob +!django.po +!django.mo +!djangojs.po +!djangojs.mo +license_manager/conf/locale/fake*/LC_MESSAGES/*.po +license_manager/conf/locale/fake*/LC_MESSAGES/*.mo +license_manager/conf/locale/messages.mo + + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# QA +coverage.xml +diff_*.html +*.report +report +venv +acceptance_tests.*.log +acceptance_tests.*.png + +# Override config files +override.cfg +private.py + +# JetBrains +.idea + +# OS X +.DS_Store + +# Editor Temp Files +*.swp + +*.trace + +docs/_build/ diff --git a/.pii_annotations.yml b/.pii_annotations.yml new file mode 100644 index 00000000..2b710e08 --- /dev/null +++ b/.pii_annotations.yml @@ -0,0 +1,35 @@ +source_path: ./ +report_path: pii_report +safelist_path: .annotation_safe_list.yml +coverage_target: 100.0 +annotations: + ".. no_pii:": + "pii_group": + - ".. pii:": + - ".. pii_types:": + choices: + - id # Unique identifier for the user which is shared across systems + - name # Used for any part of the user’s name + - username + - password + - location # Used for any part of any type address or country stored + - phone_number # Used for phone or fax numbers + - email_address + - birth_date # Used for any part of a stored birth date + - ip # IP address + - external_service # Used for external service ids or links such as social media links or usernames, website links, etc. + - biography # Any type of free-form biography field + - gender + - sex + - image + - video + - other + - ".. pii_retirement:": + choices: + - retained # Intentionally kept for legal reasons + - local_api # An API exists in this repository for retiring this information + - consumer_api # The data's consumer must implement an API for retiring this information + - third_party # A third party API exists to retire this data +extensions: + python: + - py diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..405a2da6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,17 @@ +language: python +python: + - 3.6 +sudo: false + +cache: pip +before_install: + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" +install: + - make ci_requirements + - pip install -U pip wheel codecov +script: + - make validate_translations + - make validate +after_success: + - codecov diff --git a/.tx/config b/.tx/config new file mode 100644 index 00000000..8a27a240 --- /dev/null +++ b/.tx/config @@ -0,0 +1,14 @@ +[main] +host = https://www.transifex.com + +[edx-platform.license_manager] +file_filter = license_manager/conf/locale//LC_MESSAGES/django.po +source_file = license_manager/conf/locale/en/LC_MESSAGES/django.po +source_lang = en +type = PO + +[edx-platform.license_manager-js] +file_filter = license_manager/conf/locale//LC_MESSAGES/djangojs.po +source_file = license_manager/conf/locale/en/LC_MESSAGES/djangojs.po +source_lang = en +type = PO diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..50597d3a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,7 @@ +# How to contribute + +This is an Open edX repo, and we welcome your contributions! +Please read the [contributing guidelines](http://edx.readthedocs.org/projects/edx-developer-guide/en/latest/process/index.html). + + + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..1eb391f3 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,671 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. + +EdX Inc. wishes to state, in clarification of the above license terms, that +any public, independently available web service offered over the network and +communicating with edX's copyrighted works by any form of inter-service +communication, including but not limited to Remote Procedure Call (RPC) +interfaces, is not a work based on our copyrighted work within the meaning +of the license. "Corresponding Source" of this work, or works based on this +work, as defined by the terms of this license do not include source code +files for programs used solely to provide those public, independently +available web services. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..2abdb061 --- /dev/null +++ b/Makefile @@ -0,0 +1,135 @@ +.DEFAULT_GOAL := test + +.PHONY: help clean piptools requirements ci_requirements dev_requirements \ + validation_requirements doc_requirementsprod_requirements static shell \ + test coverage isort_check isort style lint quality pii_check validate \ + migrate html_coverage upgrade extract_translation dummy_translations \ + compile_translations fake_translations pull_translations \ + push_translations start-devstack open-devstack pkg-devstack \ + detect_changed_source_translations validate_translations + +define BROWSER_PYSCRIPT +import os, webbrowser, sys +try: + from urllib import pathname2url +except: + from urllib.request import pathname2url + +webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) +endef +export BROWSER_PYSCRIPT +BROWSER := python -c "$$BROWSER_PYSCRIPT" + +# Generates a help message. Borrowed from https://github.com/pydanny/cookiecutter-djangopackage. +help: ## display this help message + @echo "Please use \`make \` where is one of" + @awk -F ':.*?## ' '/^[a-zA-Z]/ && NF==2 {printf "\033[36m %-25s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort + +clean: ## delete generated byte code and coverage reports + find . -name '*.pyc' -delete + coverage erase + rm -rf assets + rm -rf pii_report + +piptools: ## install pinned version of pip-compile and pip-sync + pip install -r requirements/pip-tools.txt + +requirements: piptools dev_requirements ## sync to default requirements + +ci_requirements: validation_requirements ## sync to requirements needed for CI checks + +dev_requirements: ## sync to requirements for local development + pip-sync -q requirements/dev.txt + +validation_requirements: ## sync to requirements for testing & code quality checking + pip-sync -q requirements/validation.txt + +doc_requirements: + pip-sync -q requirements/doc.txt + +prod_requirements: ## install requirements for production + pip-sync -q requirements/production.txt + +static: ## generate static files + python manage.py collectstatic --noinput + +shell: ## run Django shell + python manage.py shell + +test: clean ## run tests and generate coverage report + pytest + +# To be run from CI context +coverage: clean + pytest --cov-report html + $(BROWSER) htmlcov/index.html + +isort_check: ## check that isort has been run + isort --check-only -rc license_manager/ + +isort: ## run isort to sort imports in all Python files + isort --recursive --atomic license_manager/ + +style: ## run Python style checker + pylint --rcfile=pylintrc license_manager *.py + +lint: ## run Python code linting + pylint --rcfile=pylintrc license_manager *.py + +quality: style isort_check lint ## check code style and import sorting, then lint + +pii_check: ## check for PII annotations on all Django models + DJANGO_SETTINGS_MODULE=license_manager.settings.test \ + code_annotations django_find_annotations --config_file .pii_annotations.yml --lint --report --coverage + +validate: test quality pii_check ## run tests, quality, and PII annotation checks + +migrate: ## apply database migrations + python manage.py migrate + +html_coverage: ## generate and view HTML coverage report + coverage html && open htmlcov/index.html + +upgrade: export CUSTOM_COMPILE_COMMAND=make upgrade +upgrade: piptools ## update the requirements/*.txt files with the latest packages satisfying requirements/*.in + # Make sure to compile files after any other files they include! + pip-compile --upgrade -o requirements/pip-tools.txt requirements/pip-tools.in + pip-compile --upgrade -o requirements/base.txt requirements/base.in + pip-compile --upgrade -o requirements/test.txt requirements/test.in + pip-compile --upgrade -o requirements/doc.txt requirements/doc.in + pip-compile --upgrade -o requirements/quality.txt requirements/quality.in + pip-compile --upgrade -o requirements/validation.txt requirements/validation.in + pip-compile --upgrade -o requirements/dev.txt requirements/dev.in + pip-compile --upgrade -o requirements/production.txt requirements/production.in + +extract_translations: ## extract strings to be translated, outputting .mo files + python manage.py makemessages -l en -v1 -d django + python manage.py makemessages -l en -v1 -d djangojs + +dummy_translations: ## generate dummy translation (.po) files + cd license_manager && i18n_tool dummy + +compile_translations: # compile translation files, outputting .po files for each supported language + python manage.py compilemessages + +fake_translations: ## generate and compile dummy translation files + +pull_translations: ## pull translations from Transifex + tx pull -af --mode reviewed + +push_translations: ## push source translation files (.po) from Transifex + tx push -s + +start-devstack: ## run a local development copy of the server + docker-compose --x-networking up + +open-devstack: ## open a shell on the server started by start-devstack + docker exec -it license_manager /edx/app/license_manager/devstack.sh open + +pkg-devstack: ## build the license_manager image from the latest configuration and code + docker build -t license_manager:latest -f docker/build/license_manager/Dockerfile git://github.com/edx/configuration + +detect_changed_source_translations: ## check if translation files are up-to-date + cd license_manager && i18n_tool changed + +validate_translations: fake_translations detect_changed_source_translations ## install fake translations and check if translation files are up-to-date diff --git a/README.rst b/README.rst new file mode 100644 index 00000000..34ad00ac --- /dev/null +++ b/README.rst @@ -0,0 +1,38 @@ +License Management Service |Travis|_ |Codecov|_ +=================================================== +.. |Travis| image:: https://travis-ci.org/edx/license-manager.svg?branch=master +.. _Travis: https://travis-ci.org/edx/license-manager + +.. |Codecov| image:: http://codecov.io/github/edx/license-manager/coverage.svg?branch=master +.. _Codecov: http://codecov.io/github/edx/license-manager?branch=master + +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. + +Documentation +------------- +.. |ReadtheDocs| image:: https://readthedocs.org/projects/license-manager/badge/?version=latest +.. _ReadtheDocs: http://license-manager.readthedocs.io/en/latest/ + +`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. + +License +------- + +The code in this repository is licensed under version 3 of the AGPL unless otherwise noted. Please see the LICENSE_ file for details. + +.. _LICENSE: https://github.com/edx/license-manager/blob/master/LICENSE + +How To Contribute +----------------- + +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. + +Reporting Security Issues +------------------------- + +Please do not report security issues in public. Please email security@edx.org. + +Get Help +-------- + +Ask questions and discuss this project on `Slack `_ or in the `edx-code Google Group `_. diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..ed9af4aa --- /dev/null +++ b/codecov.yml @@ -0,0 +1,9 @@ +comment: off +coverage: + status: + patch: + default: + target: 100 + project: + default: + target: 100 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..24155222 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +db: + image: mysql:5.6 + container_name: db + environment: + MYSQL_ROOT_PASSWORD: "" + MYSQL_ALLOW_EMPTY_PASSWORD: "yes" + +memcache: + image: memcached:1.4.24 + container_name: memcache + +license_manager: + # Uncomment this line to use the official license_manager base image + image: license_manager:v1 + + # Uncomment the next two lines to build from a local configuration repo + #build: ../configuration + #dockerfile: docker/build/license_manager/Dockerfile + + container_name: license_manager + volumes: + - .:/edx/app/license_manager/license_manager + command: /edx/app/license_manager/devstack.sh start + ports: + - "18188:18188" # TODO: change this to your port diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..df516060 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,192 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(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/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " applehelp to make an Apple Help Book" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " coverage to run coverage check of the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/license_manager.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/license_manager.qhc" + +applehelp: + $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp + @echo + @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." + @echo "N.B. You won't be able to view it unless you put it in" \ + "~/Library/Documentation/Help or install it in your application" \ + "bundle." + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/license_manager" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/license_manager" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +coverage: + $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage + @echo "Testing of coverage in the sources finished, look at the " \ + "results in $(BUILDDIR)/coverage/python.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/__init__.py b/docs/__init__.py new file mode 100644 index 00000000..c6435c3b --- /dev/null +++ b/docs/__init__.py @@ -0,0 +1 @@ +# Included so Django's startproject command runs against the docs directory diff --git a/docs/_static/theme_overrides.css b/docs/_static/theme_overrides.css new file mode 100644 index 00000000..aad82457 --- /dev/null +++ b/docs/_static/theme_overrides.css @@ -0,0 +1,10 @@ +/* override table width restrictions */ +.wy-table-responsive table td, .wy-table-responsive table th { + /* !important prevents the common CSS stylesheets from + overriding this as on RTD they are loaded after this stylesheet */ + white-space: normal !important; +} + +.wy-table-responsive { + overflow: visible !important; +} diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..79f67f4e --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,257 @@ +# -*- coding: utf-8 -*- +# +# License Management Service documentation build configuration file, created by +# sphinx-quickstart on Sun Feb 17 11:46:20 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import os +import edx_theme + +# If you wish to publish docs to readthedocs.org you'll need to make sure to +# follow the steps here: +# https://edx-sphinx-theme.readthedocs.io/en/latest/readme.html#read-the-docs-configuration + +html_theme = 'edx_theme' +html_theme_path = [edx_theme.get_html_theme_path()] + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['edx_theme'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'License Management Service' +copyright = edx_theme.COPYRIGHT +author = u'edX' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +# modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +# html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +# html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +# html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +# html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +html_favicon = os.path.join(html_theme_path[0], 'edx_theme', 'static', 'css', 'favicon.ico') + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +# html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +# html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +# html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +# html_additional_pages = {} + +# If false, no module index is generated. +# html_domain_indices = True + +# If false, no index is generated. +# html_use_index = True + +# If true, the index is split into individual pages for each letter. +# html_split_index = False + +# If true, links to the reST sources are added to the pages. +# html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +# html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +# html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +# html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'license_managerdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # 'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'license_manager.tex', u'License Management Service Documentation', + u'edX', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +# latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +# latex_use_parts = False + +# If true, show page references after internal links. +# latex_show_pagerefs = False + +# If true, show URL addresses after external links. +# latex_show_urls = False + +# Documents to append as an appendix to all manuals. +# latex_appendices = [] + +# If false, no module index is generated. +# latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'License Management Service', u'License Management Service Documentation', + [u'edX'], 1) +] + +# If true, show URL addresses after external links. +# man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'License Management Service', u'License Management Service Documentation', + u'edX', 'License Management Service', 'License Management Service', + 'Miscellaneous' + ), +] + + +# Documents to append as an appendix to all manuals. +# texinfo_appendices = [] + +# If false, no module index is generated. +# texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +# texinfo_show_urls = 'footnote' + + +def setup(app): + app.add_stylesheet('theme_overrides.css') diff --git a/docs/features.rst b/docs/features.rst new file mode 100644 index 00000000..1479a6da --- /dev/null +++ b/docs/features.rst @@ -0,0 +1,28 @@ +Feature Toggling +================ +All new features/functionality should be released behind a feature gate. This allows us to easily disable features +in the event that an issue is discovered in production. This project uses the +`Waffle `_ library for feature gating. + +Waffle supports three types of feature gates (listed below). We typically use flags and switches since samples are +random, and not ideal for our needs. + + Flag + Enable a feature for specific users, groups, users meeting certain criteria (e.g. authenticated or staff), + or a certain percentage of visitors. + + Switch + Simple boolean, toggling a feature for all users. + + Sample + Toggle the feature for a specified percentage of the time. + + +For information on creating or updating features, refer to the +`Waffle documentation `_. + +Permanent Feature Rollout +------------------------- +Over time some features may become permanent and no longer need a feature gate around them. In such instances, the +relevant code and tests should be updated to remove the feature gate. Once the code is released, the feature flag/switch +should be deleted. diff --git a/docs/getting_started.rst b/docs/getting_started.rst new file mode 100644 index 00000000..cbe32e12 --- /dev/null +++ b/docs/getting_started.rst @@ -0,0 +1,117 @@ +Getting Started +=============== + +If you have not already done so, create/activate a `virtualenv`_. Unless otherwise stated, assume all terminal code +below is executed within the virtualenv. + +.. _virtualenv: https://virtualenvwrapper.readthedocs.org/en/latest/ + + +Initialize and Provision +------------------------ + 1. Start and provision the edX `devstack `_, as license-manager currently relies on devstack + 2. Verify that your virtual environment is active before proceeding + 3. Clone the license-manager repo and cd into that directory + 4. Run *make dev.provision* to provision a new license manager environment + 5. Run *make dev.init* to start the license manager app and run migrations + +Viewing License Manager +------------------------ +Once the server is up and running you can view the license manager at http://localhost:18188/admin. + +You can login with the username *edx@example.com* and password *edx*. + +Makefile Commands +-------------------- +The `Makefile <../Makefile>`_ includes numerous commands to start the service, but the basic commands are the following: + +Start the Docker containers to run the license manager servers + +.. code-block:: bash + + $ make dev.up + +Open the shell to the license manager container for manual commands + +.. code-block:: bash + + $ make app-shell + +Open the logs in the license manager container + +.. code-block:: bash + + $ make license-manager-logs + +Advanced Setup Outside Docker +============================= +The following is provided for informational purposes only. You can likely ignore this section. + +Local/Private Settings +---------------------- +When developing locally, it may be useful to have settings overrides that you do not wish to commit to the repository. +If you need such overrides, create a file :file:`license_manager/settings/private.py`. This file's values are +read by :file:`license_manager/settings/local.py`, but ignored by Git. + +Configure edX OAuth +------------------- +This service relies on the LMS server as the OAuth 2.0 authentication provider. + +Configuring License Manager service to communicate with other IDAs using OAuth requires registering a new client with the authentication +provider (LMS) and updating the Django settings for this project with the generated client credentials. + +A new OAuth 2.0 client can be created when using Devstack by visiting ``http://127.0.0.1:18000/admin/oauth2_provider/application/``. + 1. Click the :guilabel:`Add Application` button. + 2. Leave the user field blank. + 3. Specify the name of this service, ``License Manager service``, as the client name. + 4. Set the :guilabel:`URL` to the root path of this service: ``http://127.0.0.1:8003/``. + 5. Set the :guilabel:`Redirect URL` to the complete endpoint: ``http://127.0.0.1:18150/complete/edx-oauth2/``. + 6. Copy the :guilabel:`Client ID` and :guilabel:`Client Secret` values. They will be used later. + 7. Select :guilabel:`Confidential` as the client type. + 8. Select :guilabel:`Authorization code` as the authorization grant type. + 9. Click :guilabel:`Save`. + + + +Now that you have the client credentials, you can update your settings (ideally in +:file:`license_manager/settings/local.py`). The table below describes the relevant settings. + ++-----------------------------------+----------------------------------+--------------------------------------------------------------------------+ +| Setting | Description | Value | ++===================================+==================================+==========================================================================+ +| SOCIAL_AUTH_EDX_OAUTH2_KEY | SSO OAuth 2.0 client key | (This should be set to the value generated when the client was created.) | ++-----------------------------------+----------------------------------+--------------------------------------------------------------------------+ +| SOCIAL_AUTH_EDX_OAUTH2_SECRET | SSO OAuth 2.0 client secret | (This should be set to the value generated when the client was created.) | ++-----------------------------------+----------------------------------+--------------------------------------------------------------------------+ +| SOCIAL_AUTH_EDX_OAUTH2_URL_ROOT | OAuth 2.0 authentication URL | http://127.0.0.1:18000/oauth2 | ++-----------------------------------+----------------------------------+--------------------------------------------------------------------------+ +| 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.) | ++-----------------------------------+----------------------------------+--------------------------------------------------------------------------+ +| 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.) | ++-----------------------------------+----------------------------------+--------------------------------------------------------------------------+ + + +Run migrations +-------------- +Local installations use SQLite by default. If you choose to use another database backend, make sure you have updated +your settings and created the database (if necessary). Migrations can be run with `Django's migrate command`_. + +.. code-block:: bash + + $ python manage.py migrate + +.. _Django's migrate command: https://docs.djangoproject.com/en/1.11/ref/django-admin/#django-admin-migrate + + +Run the server +-------------- +The server can be run with `Django's runserver command`_. If you opt to run on a different port, make sure you update +OAuth2 client via LMS admin. + +.. code-block:: bash + + $ python manage.py runserver 8003 + +.. _Django's runserver command: https://docs.djangoproject.com/en/1.11/ref/django-admin/#runserver-port-or-address-port + + diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..b00830a4 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,16 @@ +.. license-manager documentation master file, created by + sphinx-quickstart on Sun Feb 17 11:46:20 2013. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +License Management Service +======================================================================= +Django backend for managing licenses and subscriptions + +.. toctree:: + :maxdepth: 2 + + getting_started + testing + features + internationalization diff --git a/docs/internationalization.rst b/docs/internationalization.rst new file mode 100644 index 00000000..2070f198 --- /dev/null +++ b/docs/internationalization.rst @@ -0,0 +1,48 @@ +Internationalization +==================== +All user-facing text content should be marked for translation. Even if this application is only run in English, our +open source users may choose to use another language. Marking content for translation ensures our users have +this choice. + +Follow the `internationalization coding guidelines`_ in the edX Developer's Guide when developing new features. + +.. _internationalization coding guidelines: http://edx.readthedocs.org/projects/edx-developer-guide/en/latest/internationalization/i18n.html + +Updating Translations +~~~~~~~~~~~~~~~~~~~~~ +This project uses `Transifex`_ to translate content. After new features are developed the translation source files +should be pushed to Transifex. Our translation community will translate the content, after which we can retrieve the +translations. + +.. _Transifex: https://www.transifex.com/ + +Pushing source translation files to Transifex requires access to the edx-platform. Request access from the Open Source +Team if you will be pushing translation files. You should also `configure the Transifex client`_ if you have not done so +already. + +.. _configure the Transifex client: http://docs.transifex.com/client/config/ + +The `make` targets listed below can be used to push or pull translations. + +.. list-table:: + :widths: 25 75 + :header-rows: 1 + + * - Target + - Description + * - pull_translations + - Pull translations from Transifex + * - push_translations + - Push source translation files to Transifex + +Fake Translations +~~~~~~~~~~~~~~~~~ +As you develop features it may be helpful to know which strings have been marked for translation, and which are not. +Use the `fake_translations` make target for this purpose. This target will extract all strings marked for translation, +generate fake translations in the Esperanto (eo) language directory, and compile the translations. + +You can trigger the display of the translations by setting your browser's language to Esperanto (eo), and navigating to +a page on the site. Instead of plain English strings, you should see specially-accented English strings that look like this: + + Thé Fütüré øf Ønlïné Édüçätïøn Ⱡσяєм ι# Før änýøné, änýwhéré, änýtïmé Ⱡσяєм # + diff --git a/docs/testing.rst b/docs/testing.rst new file mode 100644 index 00000000..5efd6106 --- /dev/null +++ b/docs/testing.rst @@ -0,0 +1,14 @@ +Testing +======= + +The command below runs the Python tests and code quality validation—Pylint and Pycodestyle. + +.. code-block:: bash + + $ make validate + +Code quality validation can be run independently with: + +.. code-block:: bash + + $ make quality diff --git a/license_manager/__init__.py b/license_manager/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/license_manager/apps/__init__.py b/license_manager/apps/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/license_manager/apps/api/__init__.py b/license_manager/apps/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/license_manager/apps/api/models.py b/license_manager/apps/api/models.py new file mode 100644 index 00000000..ff4afbbd --- /dev/null +++ b/license_manager/apps/api/models.py @@ -0,0 +1,4 @@ +# Models that can be shared across multiple versions of the API +# should be created here. As the API evolves, models may become more +# specific to a particular version of the API. In this case, the models +# in question should be moved to versioned sub-package. diff --git a/license_manager/apps/api/serializers.py b/license_manager/apps/api/serializers.py new file mode 100644 index 00000000..69067505 --- /dev/null +++ b/license_manager/apps/api/serializers.py @@ -0,0 +1,4 @@ +# Serializers that can be shared across multiple versions of the API +# should be created here. As the API evolves, serializers may become more +# specific to a particular version of the API. In this case, the serializers +# in question should be moved to versioned sub-package. diff --git a/license_manager/apps/api/tests/__init__.py b/license_manager/apps/api/tests/__init__.py new file mode 100644 index 00000000..81c61f8e --- /dev/null +++ b/license_manager/apps/api/tests/__init__.py @@ -0,0 +1 @@ +# Create your tests in sub-packages prefixed with "test_" (e.g. test_models). diff --git a/license_manager/apps/api/urls.py b/license_manager/apps/api/urls.py new file mode 100644 index 00000000..c3561102 --- /dev/null +++ b/license_manager/apps/api/urls.py @@ -0,0 +1,15 @@ +""" +Root API URLs. + +All API URLs should be versioned, so urlpatterns should only +contain namespaces for the active versions of the API. +""" +from django.conf.urls import include, url + +from license_manager.apps.api.v1 import urls as v1_urls + + +app_name = 'api' +urlpatterns = [ + url(r'^v1/', include(v1_urls)), +] diff --git a/license_manager/apps/api/v1/__init__.py b/license_manager/apps/api/v1/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/license_manager/apps/api/v1/tests/__init__.py b/license_manager/apps/api/v1/tests/__init__.py new file mode 100644 index 00000000..d2557275 --- /dev/null +++ b/license_manager/apps/api/v1/tests/__init__.py @@ -0,0 +1 @@ +# Create your tests in sub-packages prefixed with "test_" (e.g. test_views). diff --git a/license_manager/apps/api/v1/urls.py b/license_manager/apps/api/v1/urls.py new file mode 100644 index 00000000..8f654b2c --- /dev/null +++ b/license_manager/apps/api/v1/urls.py @@ -0,0 +1,4 @@ +""" API v1 URLs. """ + +app_name = 'v1' +urlpatterns = [] diff --git a/license_manager/apps/api/v1/views.py b/license_manager/apps/api/v1/views.py new file mode 100644 index 00000000..60f00ef0 --- /dev/null +++ b/license_manager/apps/api/v1/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/license_manager/apps/core/__init__.py b/license_manager/apps/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/license_manager/apps/core/admin.py b/license_manager/apps/core/admin.py new file mode 100644 index 00000000..a9308c85 --- /dev/null +++ b/license_manager/apps/core/admin.py @@ -0,0 +1,22 @@ +""" Admin configuration for core models. """ + +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from django.utils.translation import ugettext_lazy as _ + +from license_manager.apps.core.models import User + + +class CustomUserAdmin(UserAdmin): + """ Admin configuration for the custom User model. """ + list_display = ('username', 'email', 'full_name', 'first_name', 'last_name', 'is_staff') + fieldsets = ( + (None, {'fields': ('username', 'password')}), + (_('Personal info'), {'fields': ('full_name', 'first_name', 'last_name', 'email')}), + (_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser', + 'groups', 'user_permissions')}), + (_('Important dates'), {'fields': ('last_login', 'date_joined')}), + ) + + +admin.site.register(User, CustomUserAdmin) diff --git a/license_manager/apps/core/constants.py b/license_manager/apps/core/constants.py new file mode 100644 index 00000000..d6f5f320 --- /dev/null +++ b/license_manager/apps/core/constants.py @@ -0,0 +1,7 @@ +""" Constants for the core app. """ + + +class Status: + """Health statuses.""" + OK = u"OK" + UNAVAILABLE = u"UNAVAILABLE" diff --git a/license_manager/apps/core/context_processors.py b/license_manager/apps/core/context_processors.py new file mode 100644 index 00000000..7d3ba351 --- /dev/null +++ b/license_manager/apps/core/context_processors.py @@ -0,0 +1,9 @@ +""" Core context processors. """ +from django.conf import settings + + +def core(_request): + """ Site-wide context processor. """ + return { + 'platform_name': settings.PLATFORM_NAME + } diff --git a/license_manager/apps/core/migrations/__init__.py b/license_manager/apps/core/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/license_manager/apps/core/models.py b/license_manager/apps/core/models.py new file mode 100644 index 00000000..7470475b --- /dev/null +++ b/license_manager/apps/core/models.py @@ -0,0 +1,39 @@ +""" Core models. """ + +from django.contrib.auth.models import AbstractUser +from django.db import models +from django.utils.encoding import python_2_unicode_compatible +from django.utils.translation import ugettext_lazy as _ + + +class User(AbstractUser): + """ + Custom user model for use with python-social-auth via edx-auth-backends. + + .. pii: Stores full name, username, and email address for a user. + .. pii_types: name, username, email_address + .. pii_retirement: local_api + + """ + full_name = models.CharField(_('Full Name'), max_length=255, blank=True, null=True) + + @property + def access_token(self): + """ + Returns an OAuth2 access token for this user, if one exists; otherwise None. + Assumes user has authenticated at least once with the OAuth2 provider (LMS). + """ + try: + return self.social_auth.first().extra_data[u'access_token'] # pylint: disable=no-member + except Exception: # pylint: disable=broad-except + return None + + class Meta: + get_latest_by = 'date_joined' + + def get_full_name(self): + return self.full_name or super(User, self).get_full_name() + + @python_2_unicode_compatible + def __str__(self): + return str(self.get_full_name()) diff --git a/license_manager/apps/core/tests/__init__.py b/license_manager/apps/core/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/license_manager/apps/core/tests/test_context_processors.py b/license_manager/apps/core/tests/test_context_processors.py new file mode 100644 index 00000000..f3eae8e5 --- /dev/null +++ b/license_manager/apps/core/tests/test_context_processors.py @@ -0,0 +1,17 @@ +""" Context processor tests. """ + +from django.test import RequestFactory, TestCase, override_settings + +from license_manager.apps.core.context_processors import core + + +PLATFORM_NAME = 'Test Platform' + + +class CoreContextProcessorTests(TestCase): + """ Tests for core.context_processors.core """ + + @override_settings(PLATFORM_NAME=PLATFORM_NAME) + def test_core(self): + request = RequestFactory().get('/') + self.assertDictEqual(core(request), {'platform_name': PLATFORM_NAME}) diff --git a/license_manager/apps/core/tests/test_models.py b/license_manager/apps/core/tests/test_models.py new file mode 100644 index 00000000..a7925557 --- /dev/null +++ b/license_manager/apps/core/tests/test_models.py @@ -0,0 +1,45 @@ +""" Tests for core models. """ + +from django.test import TestCase +from django_dynamic_fixture import G +from social_django.models import UserSocialAuth + +from license_manager.apps.core.models import User + + +class UserTests(TestCase): + """ User model tests. """ + TEST_CONTEXT = {'foo': 'bar', 'baz': None} + + def test_access_token(self): + user = G(User) + self.assertIsNone(user.access_token) + + social_auth = G(UserSocialAuth, user=user) + self.assertIsNone(user.access_token) + + access_token = 'My voice is my passport. Verify me.' + social_auth.extra_data['access_token'] = access_token + social_auth.save() + self.assertEqual(user.access_token, access_token) + + def test_get_full_name(self): + """ Test that the user model concatenates first and last name if the full name is not set. """ + full_name = 'George Costanza' + user = G(User, full_name=full_name) + self.assertEqual(user.get_full_name(), full_name) + + first_name = 'Jerry' + last_name = 'Seinfeld' + user = G(User, full_name=None, first_name=first_name, last_name=last_name) + expected = '{first_name} {last_name}'.format(first_name=first_name, last_name=last_name) + self.assertEqual(user.get_full_name(), expected) + + user = G(User, full_name=full_name, first_name=first_name, last_name=last_name) + self.assertEqual(user.get_full_name(), full_name) + + def test_string(self): + """Verify that the model's string method returns the user's full name.""" + full_name = 'Bob' + user = G(User, full_name=full_name) + self.assertEqual(str(user), full_name) diff --git a/license_manager/apps/core/tests/test_views.py b/license_manager/apps/core/tests/test_views.py new file mode 100644 index 00000000..eda8e8e9 --- /dev/null +++ b/license_manager/apps/core/tests/test_views.py @@ -0,0 +1,76 @@ +"""Test core.views.""" + +import mock +from django.conf import settings +from django.contrib.auth import get_user_model +from django.db import DatabaseError +from django.test import TestCase +from django.test.utils import override_settings +from django.urls import reverse + +from license_manager.apps.core.constants import Status + + +User = get_user_model() + + +class HealthTests(TestCase): + """Tests of the health endpoint.""" + + def test_all_services_available(self): + """Test that the endpoint reports when all services are healthy.""" + self._assert_health(200, Status.OK, Status.OK) + + def test_database_outage(self): + """Test that the endpoint reports when the database is unavailable.""" + with mock.patch('django.db.backends.base.base.BaseDatabaseWrapper.cursor', side_effect=DatabaseError): + self._assert_health(503, Status.UNAVAILABLE, Status.UNAVAILABLE) + + def _assert_health(self, status_code, overall_status, database_status): + """Verify that the response matches expectations.""" + response = self.client.get(reverse('health')) + self.assertEqual(response.status_code, status_code) + self.assertEqual(response['content-type'], 'application/json') + + expected_data = { + 'overall_status': overall_status, + 'detailed_status': { + 'database_status': database_status + } + } + + self.assertJSONEqual(response.content, expected_data) + + +class AutoAuthTests(TestCase): + """ Auto Auth view tests. """ + AUTO_AUTH_PATH = reverse('auto_auth') + + @override_settings(ENABLE_AUTO_AUTH=False) + def test_setting_disabled(self): + """When the ENABLE_AUTO_AUTH setting is False, the view should raise a 404.""" + response = self.client.get(self.AUTO_AUTH_PATH) + self.assertEqual(response.status_code, 404) + + @override_settings(ENABLE_AUTO_AUTH=True) + def test_setting_enabled(self): + """ + When ENABLE_AUTO_AUTH is set to True, the view should create and authenticate + a new User with superuser permissions. + """ + original_user_count = User.objects.count() + response = self.client.get(self.AUTO_AUTH_PATH) + + # Verify that a redirect has occured and that a new user has been created + self.assertEqual(response.status_code, 302) + self.assertEqual(User.objects.count(), original_user_count + 1) + + # Get the latest user + user = User.objects.latest() + + # Verify that the user is logged in and that their username has the expected prefix + self.assertEqual(int(self.client.session['_auth_user_id']), user.pk) + self.assertTrue(user.username.startswith(settings.AUTO_AUTH_USERNAME_PREFIX)) + + # Verify that the user has superuser permissions + self.assertTrue(user.is_superuser) diff --git a/license_manager/apps/core/views.py b/license_manager/apps/core/views.py new file mode 100644 index 00000000..0b5ea972 --- /dev/null +++ b/license_manager/apps/core/views.py @@ -0,0 +1,86 @@ +""" Core views. """ +import logging +import uuid + +from django.conf import settings +from django.contrib.auth import authenticate, get_user_model, login +from django.db import DatabaseError, connection, transaction +from django.http import Http404, JsonResponse +from django.shortcuts import redirect +from django.views.generic import View + +from license_manager.apps.core.constants import Status + + +logger = logging.getLogger(__name__) +User = get_user_model() + + +@transaction.non_atomic_requests +def health(_): + """Allows a load balancer to verify this service is up. + + Checks the status of the database connection on which this service relies. + + Returns: + HttpResponse: 200 if the service is available, with JSON data indicating the health of each required service + HttpResponse: 503 if the service is unavailable, with JSON data indicating the health of each required service + + Example: + >>> response = requests.get('https://license-manager.edx.org/health') + >>> response.status_code + 200 + >>> response.content + '{"overall_status": "OK", "detailed_status": {"database_status": "OK", "lms_status": "OK"}}' + """ + + try: + cursor = connection.cursor() + cursor.execute("SELECT 1") + cursor.fetchone() + cursor.close() + database_status = Status.OK + except DatabaseError: + database_status = Status.UNAVAILABLE + + overall_status = Status.OK if (database_status == Status.OK) else Status.UNAVAILABLE + + data = { + 'overall_status': overall_status, + 'detailed_status': { + 'database_status': database_status, + }, + } + + if overall_status == Status.OK: + return JsonResponse(data) + else: + return JsonResponse(data, status=503) + + +class AutoAuth(View): + """Creates and authenticates a new User with superuser permissions. + + If the ENABLE_AUTO_AUTH setting is not True, returns a 404. + """ + + def get(self, request): + """ + Create a new User. + + Raises Http404 if auto auth is not enabled. + """ + if not getattr(settings, 'ENABLE_AUTO_AUTH', None): + raise Http404 + + username_prefix = getattr(settings, 'AUTO_AUTH_USERNAME_PREFIX', 'auto_auth_') + + # Create a new user with staff permissions + username = password = username_prefix + uuid.uuid4().hex[0:20] + User.objects.create_superuser(username, email=None, password=password) + + # Log in the new user + user = authenticate(username=username, password=password) + login(request, user) + + return redirect('/') diff --git a/license_manager/conf/locale/config.yaml b/license_manager/conf/locale/config.yaml new file mode 100644 index 00000000..d1c618e8 --- /dev/null +++ b/license_manager/conf/locale/config.yaml @@ -0,0 +1,85 @@ +# Configuration for i18n workflow. + +locales: + - en # English - Source Language + - am # Amharic + - ar # Arabic + - az # Azerbaijani + - bg_BG # Bulgarian (Bulgaria) + - bn_BD # Bengali (Bangladesh) + - bn_IN # Bengali (India) + - bs # Bosnian + - ca # Catalan + - ca@valencia # Catalan (Valencia) + - cs # Czech + - cy # Welsh + - da # Danish + - de_DE # German (Germany) + - el # Greek + - en # English + - en_GB # English (United Kingdom) + # Don't pull these until we figure out why pages randomly display in these locales, + # when the user's browser is in English and the user is not logged in. + # - en@lolcat # LOLCAT English + # - en@pirate # Pirate English + - es_419 # Spanish (Latin America) + - es_AR # Spanish (Argentina) + - es_EC # Spanish (Ecuador) + - es_ES # Spanish (Spain) + - es_MX # Spanish (Mexico) + - es_PE # Spanish (Peru) + - et_EE # Estonian (Estonia) + - eu_ES # Basque (Spain) + - fa # Persian + - fa_IR # Persian (Iran) + - fi_FI # Finnish (Finland) + - fil # Filipino + - fr # French + - gl # Galician + - gu # Gujarati + - he # Hebrew + - hi # Hindi + - hr # Croatian + - hu # Hungarian + - hy_AM # Armenian (Armenia) + - id # Indonesian + - it_IT # Italian (Italy) + - ja_JP # Japanese (Japan) + - kk_KZ # Kazakh (Kazakhstan) + - km_KH # Khmer (Cambodia) + - kn # Kannada + - ko_KR # Korean (Korea) + - lt_LT # Lithuanian (Lithuania) + - ml # Malayalam + - mn # Mongolian + - mr # Marathi + - ms # Malay + - nb # Norwegian Bokmål + - ne # Nepali + - nl_NL # Dutch (Netherlands) + - or # Oriya + - pl # Polish + - pt_BR # Portuguese (Brazil) + - pt_PT # Portuguese (Portugal) + - ro # Romanian + - ru # Russian + - si # Sinhala + - sk # Slovak + - sl # Slovenian + - sq # Albanian + - sr # Serbian + - ta # Tamil + - te # Telugu + - th # Thai + - tr_TR # Turkish (Turkey) + - uk # Ukranian + - ur # Urdu + - uz # Uzbek + - vi # Vietnamese + - zh_CN # Chinese (China) + - zh_HK # Chinese (Hong Kong) + - zh_TW # Chinese (Taiwan) + +# The locales used for fake-accented English, for testing. +dummy_locales: + - eo diff --git a/license_manager/settings/__init__.py b/license_manager/settings/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/license_manager/settings/base.py b/license_manager/settings/base.py new file mode 100644 index 00000000..5d429965 --- /dev/null +++ b/license_manager/settings/base.py @@ -0,0 +1,271 @@ +import os +from os.path import abspath, dirname, join + +from corsheaders.defaults import default_headers as corsheaders_default_headers + +# PATH vars +here = lambda *x: join(abspath(dirname(__file__)), *x) +PROJECT_ROOT = here("..") +root = lambda *x: join(abspath(PROJECT_ROOT), *x) + + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = os.environ.get('LICENSE_MANAGER_SECRET_KEY', 'insecure-secret-key') + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = False + +ALLOWED_HOSTS = [] + +# Application definition + +INSTALLED_APPS = ( + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles' +) + +THIRD_PARTY_APPS = ( + 'corsheaders', + 'csrf.apps.CsrfAppConfig', # Enables frontend apps to retrieve CSRF tokens + 'rest_framework', + 'rest_framework_swagger', + 'social_django', + 'waffle', +) + +PROJECT_APPS = ( + 'license_manager.apps.core', + 'license_manager.apps.api', +) + +INSTALLED_APPS += THIRD_PARTY_APPS +INSTALLED_APPS += PROJECT_APPS + +MIDDLEWARE = ( + # Resets RequestCache utility for added safety. + 'edx_django_utils.cache.middleware.RequestCacheMiddleware', + # Enables monitoring utility for writing custom metrics. + 'edx_django_utils.monitoring.middleware.MonitoringCustomMetricsMiddleware', + 'corsheaders.middleware.CorsMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.locale.LocaleMiddleware', + 'django.middleware.common.CommonMiddleware', + 'edx_rest_framework_extensions.auth.jwt.middleware.JwtAuthCookieMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'social_django.middleware.SocialAuthExceptionMiddleware', + 'waffle.middleware.WaffleMiddleware', + # Enables force_django_cache_miss functionality for TieredCache. + 'edx_django_utils.cache.middleware.TieredCacheMiddleware', + # Outputs monitoring metrics for a request. + 'edx_rest_framework_extensions.middleware.RequestMetricsMiddleware', + # Ensures proper DRF permissions in support of JWTs + 'edx_rest_framework_extensions.auth.jwt.middleware.EnsureJWTAuthSettingsMiddleware', +) + +# Enable CORS +CORS_ALLOW_CREDENTIALS = True +CORS_ALLOW_HEADERS = corsheaders_default_headers + ( + 'use-jwt-cookie', +) +CORS_ORIGIN_WHITELIST = [] + +ROOT_URLCONF = 'license_manager.urls' + +# Python dotted path to the WSGI application used by Django's runserver. +WSGI_APPLICATION = 'license_manager.wsgi.application' + +# Database +# https://docs.djangoproject.com/en/1.11/ref/settings/#databases +# Set this value in the environment-specific files (e.g. local.py, production.py, test.py) +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.', + 'NAME': '', + 'USER': '', + 'PASSWORD': '', + 'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP. + 'PORT': '', # Set to empty string for default. + } +} + +# Internationalization +# https://docs.djangoproject.com/en/dev/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + +LOCALE_PATHS = ( + root('conf', 'locale'), +) + + +# MEDIA CONFIGURATION +# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root +MEDIA_ROOT = root('media') + +# See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url +MEDIA_URL = '/media/' +# END MEDIA CONFIGURATION + + +# STATIC FILE CONFIGURATION +# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root +STATIC_ROOT = root('assets') + +# See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url +STATIC_URL = '/static/' + +# See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS +STATICFILES_DIRS = ( + root('static'), +) + +# TEMPLATE CONFIGURATION +# See: https://docs.djangoproject.com/en/1.11/ref/settings/#templates +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'APP_DIRS': True, + 'DIRS': ( + root('templates'), + ), + 'OPTIONS': { + 'context_processors': ( + 'django.contrib.auth.context_processors.auth', + 'django.template.context_processors.debug', + 'django.template.context_processors.i18n', + 'django.template.context_processors.media', + 'django.template.context_processors.static', + 'django.template.context_processors.tz', + 'django.contrib.messages.context_processors.messages', + 'license_manager.apps.core.context_processors.core', + ), + 'debug': True, # Django will only display debug pages if the global DEBUG setting is set to True. + } + }, +] +# END TEMPLATE CONFIGURATION + + +# COOKIE CONFIGURATION +# The purpose of customizing the cookie names is to avoid conflicts when +# multiple Django services are running behind the same hostname. +# Detailed information at: https://docs.djangoproject.com/en/dev/ref/settings/ +SESSION_COOKIE_NAME = 'license_manager_sessionid' +CSRF_COOKIE_NAME = 'license_manager_csrftoken' +LANGUAGE_COOKIE_NAME = 'license_manager_language' +# END COOKIE CONFIGURATION + +CSRF_COOKIE_SECURE = False +CSRF_TRUSTED_ORIGINS = [] + +# AUTHENTICATION CONFIGURATION +LOGIN_URL = '/login/' +LOGOUT_URL = '/logout/' + +AUTH_USER_MODEL = 'core.User' + +AUTHENTICATION_BACKENDS = ( + 'auth_backends.backends.EdXOAuth2', + 'django.contrib.auth.backends.ModelBackend', +) + +ENABLE_AUTO_AUTH = False +AUTO_AUTH_USERNAME_PREFIX = 'auto_auth_' + +SOCIAL_AUTH_STRATEGY = 'auth_backends.strategies.EdxDjangoStrategy' + +# Set these to the correct values for your OAuth2 provider (e.g., LMS) +SOCIAL_AUTH_EDX_OAUTH2_KEY = 'replace-me' +SOCIAL_AUTH_EDX_OAUTH2_SECRET = 'replace-me' +SOCIAL_AUTH_EDX_OAUTH2_URL_ROOT = 'replace-me' +SOCIAL_AUTH_EDX_OAUTH2_LOGOUT_URL = 'replace-me' +BACKEND_SERVICE_EDX_OAUTH2_KEY = 'replace-me' +BACKEND_SERVICE_EDX_OAUTH2_SECRET = 'replace-me' + +JWT_AUTH = { + 'JWT_ISSUER': 'http://127.0.0.1:8000/oauth2', + 'JWT_ALGORITHM': 'HS256', + 'JWT_VERIFY_EXPIRATION': True, + 'JWT_PAYLOAD_GET_USERNAME_HANDLER': lambda d: d.get('preferred_username'), + 'JWT_LEEWAY': 1, + 'JWT_DECODE_HANDLER': 'edx_rest_framework_extensions.auth.jwt.decoder.jwt_decode_handler', + 'JWT_PUBLIC_SIGNING_JWK_SET': None, + 'JWT_AUTH_COOKIE_HEADER_PAYLOAD': 'edx-jwt-cookie-header-payload', + 'JWT_AUTH_COOKIE_SIGNATURE': 'edx-jwt-cookie-signature', + 'JWT_AUTH_REFRESH_COOKIE': 'edx-jwt-refresh-cookie', +} + +# Request the user's permissions in the ID token +EXTRA_SCOPE = ['permissions'] + +# TODO Set this to another (non-staff, ideally) path. +LOGIN_REDIRECT_URL = '/admin/' +# END AUTHENTICATION CONFIGURATION + + +# OPENEDX-SPECIFIC CONFIGURATION +PLATFORM_NAME = 'Your Platform Name Here' +# END OPENEDX-SPECIFIC CONFIGURATION + +# Set up logging for development use (logging to stdout) +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'standard': { + 'format': '%(asctime)s %(levelname)s %(process)d ' + '[%(name)s] %(filename)s:%(lineno)d - %(message)s', + }, + }, + 'handlers': { + 'console': { + 'level': 'INFO', + 'class': 'logging.StreamHandler', + 'formatter': 'standard', + 'stream': 'ext://sys.stdout', + }, + }, + 'loggers': { + 'django': { + 'handlers': ['console'], + 'propagate': True, + 'level': 'INFO' + }, + 'requests': { + 'handlers': ['console'], + 'propagate': True, + 'level': 'WARNING' + }, + 'factory': { + 'handlers': ['console'], + 'propagate': True, + 'level': 'WARNING' + }, + 'django.request': { + 'handlers': ['console'], + 'propagate': True, + 'level': 'WARNING' + }, + '': { + 'handlers': ['console'], + 'level': 'DEBUG', + 'propagate': False + }, + } +} diff --git a/license_manager/settings/devstack.py b/license_manager/settings/devstack.py new file mode 100644 index 00000000..88b9372b --- /dev/null +++ b/license_manager/settings/devstack.py @@ -0,0 +1,24 @@ +from license_manager.settings.local import * + +# Generic OAuth2 variables irrespective of SSO/backend service key types. +OAUTH2_PROVIDER_URL = 'http://edx.devstack.lms:18000/oauth2' + +# OAuth2 variables specific to social-auth/SSO login use case. +SOCIAL_AUTH_EDX_OAUTH2_KEY = os.environ.get('SOCIAL_AUTH_EDX_OAUTH2_KEY', 'license-manager-sso-key') +SOCIAL_AUTH_EDX_OAUTH2_SECRET = os.environ.get('SOCIAL_AUTH_EDX_OAUTH2_SECRET', 'license-manager-sso-secret') +SOCIAL_AUTH_EDX_OAUTH2_ISSUER = os.environ.get('SOCIAL_AUTH_EDX_OAUTH2_ISSUER', 'http://localhost:18000') +SOCIAL_AUTH_EDX_OAUTH2_URL_ROOT = os.environ.get('SOCIAL_AUTH_EDX_OAUTH2_URL_ROOT', 'http://edx.devstack.lms:18000') +SOCIAL_AUTH_EDX_OAUTH2_LOGOUT_URL = os.environ.get('SOCIAL_AUTH_EDX_OAUTH2_LOGOUT_URL', 'http://localhost:18000/logout') +SOCIAL_AUTH_EDX_OAUTH2_PUBLIC_URL_ROOT = os.environ.get( + 'SOCIAL_AUTH_EDX_OAUTH2_PUBLIC_URL_ROOT', 'http://localhost:18000', +) + +# OAuth2 variables specific to backend service API calls. +BACKEND_SERVICE_EDX_OAUTH2_KEY = os.environ.get('BACKEND_SERVICE_EDX_OAUTH2_KEY', 'license-manager-backend-service-key') +BACKEND_SERVICE_EDX_OAUTH2_SECRET = os.environ.get('BACKEND_SERVICE_EDX_OAUTH2_SECRET', 'license-manager-backend-service-secret') + +JWT_AUTH.update({ + 'JWT_SECRET_KEY': SOCIAL_AUTH_EDX_OAUTH2_SECRET, + 'JWT_ISSUER': 'http://localhost:18000/oauth2' + 'JWT_AUDIENCE': SOCIAL_AUTH_EDX_OAUTH2_KEY, +}) diff --git a/license_manager/settings/local.py b/license_manager/settings/local.py new file mode 100644 index 00000000..747e7af8 --- /dev/null +++ b/license_manager/settings/local.py @@ -0,0 +1,68 @@ +from license_manager.settings.base import * + +DEBUG = True + +# CACHE CONFIGURATION +# See: https://docs.djangoproject.com/en/dev/ref/settings/#caches +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + } +} +# END CACHE CONFIGURATION + +# DATABASE CONFIGURATION +# See: https://docs.djangoproject.com/en/dev/ref/settings/#databases +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': root('default.db'), + 'USER': '', + 'PASSWORD': '', + 'HOST': '', + 'PORT': '', + } +} +# END DATABASE CONFIGURATION + +# EMAIL CONFIGURATION +# See: https://docs.djangoproject.com/en/dev/ref/settings/#email-backend +EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' +# END EMAIL CONFIGURATION + +# TOOLBAR CONFIGURATION +# See: http://django-debug-toolbar.readthedocs.org/en/latest/installation.html +if os.environ.get('ENABLE_DJANGO_TOOLBAR', False): + INSTALLED_APPS += ( + 'debug_toolbar', + ) + + MIDDLEWARE_CLASSES += ( + 'debug_toolbar.middleware.DebugToolbarMiddleware', + ) + + DEBUG_TOOLBAR_PATCH_SETTINGS = False + +INTERNAL_IPS = ('127.0.0.1',) +# END TOOLBAR CONFIGURATION + +# AUTHENTICATION +# Use a non-SSL URL for authorization redirects +SOCIAL_AUTH_REDIRECT_IS_HTTPS = False + +# Generic OAuth2 variables irrespective of SSO/backend service key types. +OAUTH2_PROVIDER_URL = 'http://localhost:18000/oauth2' + +JWT_AUTH.update({ + 'JWT_ALGORITHM': 'HS256', + 'JWT_SECRET_KEY': SOCIAL_AUTH_EDX_OAUTH2_SECRET, + 'JWT_ISSUER': OAUTH2_PROVIDER_URL, + 'JWT_AUDIENCE': SOCIAL_AUTH_EDX_OAUTH2_KEY, +}) + +ENABLE_AUTO_AUTH = True + +##################################################################### +# Lastly, see if the developer has any local overrides. +if os.path.isfile(join(dirname(abspath(__file__)), 'private.py')): + from .private import * # pylint: disable=import-error diff --git a/license_manager/settings/private.py.example b/license_manager/settings/private.py.example new file mode 100644 index 00000000..7c10fb8e --- /dev/null +++ b/license_manager/settings/private.py.example @@ -0,0 +1,5 @@ +SOCIAL_AUTH_EDX_OAUTH2_KEY = 'replace-me' +SOCIAL_AUTH_EDX_OAUTH2_SECRET = 'replace-me' +SOCIAL_AUTH_EDX_OAUTH2_URL_ROOT = 'http://127.0.0.1:8000/oauth' +BACKEND_SERVICE_EDX_OAUTH2_KEY = 'license-manager-backend-service-key' +BACKEND_SERVICE_EDX_OAUTH2_SECRET = 'license-manager-backend-service-secret' diff --git a/license_manager/settings/production.py b/license_manager/settings/production.py new file mode 100644 index 00000000..49cdda9e --- /dev/null +++ b/license_manager/settings/production.py @@ -0,0 +1,55 @@ +from os import environ +import yaml + +from license_manager.settings.base import * +from license_manager.settings.utils import get_env_setting + + +DEBUG = False +TEMPLATE_DEBUG = DEBUG + +ALLOWED_HOSTS = ['*'] + +LOGGING['handlers']['local']['level'] = 'INFO' + +# Keep track of the names of settings that represent dicts. Instead of overriding the values in base.py, +# the values read from disk should UPDATE the pre-configured dicts. +DICT_UPDATE_KEYS = ('JWT_AUTH',) + +# This may be overridden by the YAML in license_manager_CFG, +# but it should be here as a default. +MEDIA_STORAGE_BACKEND = {} +FILE_STORAGE_BACKEND = {} + +CONFIG_FILE = get_env_setting('LICENSE_MANAGER_CFG') +with open(CONFIG_FILE, encoding='utf-8') as f: + config_from_yaml = yaml.load(f) + + # Remove the items that should be used to update dicts, and apply them separately rather + # than pumping them into the local vars. + dict_updates = {key: config_from_yaml.pop(key, None) for key in DICT_UPDATE_KEYS} + + for key, value in dict_updates.items(): + if value: + vars()[key].update(value) + + vars().update(config_from_yaml) + + # Unpack the media and files storage backend settings for django storages. + # These dicts are not Django settings themselves, but they contain a mapping + # of Django settings. + vars().update(FILE_STORAGE_BACKEND) + vars().update(MEDIA_STORAGE_BACKEND) +2 + +DB_OVERRIDES = dict( + PASSWORD=environ.get('DB_MIGRATION_PASS', DATABASES['default']['PASSWORD']), + ENGINE=environ.get('DB_MIGRATION_ENGINE', DATABASES['default']['ENGINE']), + USER=environ.get('DB_MIGRATION_USER', DATABASES['default']['USER']), + NAME=environ.get('DB_MIGRATION_NAME', DATABASES['default']['NAME']), + HOST=environ.get('DB_MIGRATION_HOST', DATABASES['default']['HOST']), + PORT=environ.get('DB_MIGRATION_PORT', DATABASES['default']['PORT']), +) + +for override, value in DB_OVERRIDES.iteritems(): + DATABASES['default'][override] = value diff --git a/license_manager/settings/test.py b/license_manager/settings/test.py new file mode 100644 index 00000000..d7af9809 --- /dev/null +++ b/license_manager/settings/test.py @@ -0,0 +1,17 @@ +import os + +from license_manager.settings.base import * + + +# IN-MEMORY TEST DATABASE +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': ':memory:', + 'USER': '', + 'PASSWORD': '', + 'HOST': '', + 'PORT': '', + }, +} +# END IN-MEMORY TEST DATABASE diff --git a/license_manager/settings/utils.py b/license_manager/settings/utils.py new file mode 100644 index 00000000..0065bf01 --- /dev/null +++ b/license_manager/settings/utils.py @@ -0,0 +1,12 @@ +from os import environ + +from django.core.exceptions import ImproperlyConfigured + + +def get_env_setting(setting): + """ Get the environment setting or raise exception """ + try: + return environ[setting] + except KeyError: + error_msg = "Set the [%s] env variable!" % setting + raise ImproperlyConfigured(error_msg) diff --git a/license_manager/static/.keep b/license_manager/static/.keep new file mode 100644 index 00000000..e69de29b diff --git a/license_manager/templates/.keep b/license_manager/templates/.keep new file mode 100644 index 00000000..e69de29b diff --git a/license_manager/urls.py b/license_manager/urls.py new file mode 100644 index 00000000..e0a9e4e5 --- /dev/null +++ b/license_manager/urls.py @@ -0,0 +1,44 @@ +"""license_manager URL Configuration +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.11/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Add an import: from blog import urls as blog_urls + 2. Add a URL to urlpatterns: url(r'^blog/', include(blog_urls)) +""" + +import os + +from auth_backends.urls import oauth2_urlpatterns +from django.conf import settings +from django.conf.urls import include, url +from django.contrib import admin +from rest_framework_swagger.views import get_swagger_view + +from license_manager.apps.api import urls as api_urls +from license_manager.apps.core import views as core_views + + +admin.autodiscover() + +urlpatterns = [ + url(r'^admin/', admin.site.urls), + url(r'^api/', include(api_urls)), + url(r'^api-docs/', get_swagger_view(title='License Manager API')), + # Use the same auth views for all logins, including those originating from the browseable API. + url(r'^api-auth/', include(oauth2_urlpatterns)), + url(r'^auto_auth/$', core_views.AutoAuth.as_view(), name='auto_auth'), + url(r'^health/$', core_views.health, name='health'), +] + +if settings.DEBUG and os.environ.get('ENABLE_DJANGO_TOOLBAR', False): # pragma: no cover + # Disable pylint import error because we don't install django-debug-toolbar + # for CI build + import debug_toolbar # pylint: disable=import-error + urlpatterns.append(url(r'^__debug__/', include(debug_toolbar.urls))) diff --git a/license_manager/wsgi.py b/license_manager/wsgi.py new file mode 100644 index 00000000..f2219390 --- /dev/null +++ b/license_manager/wsgi.py @@ -0,0 +1,22 @@ +""" +WSGI config for license_manager. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ +""" +import os +from os.path import abspath, dirname +from sys import path + +from django.core.wsgi import get_wsgi_application + + +SITE_ROOT = dirname(dirname(abspath(__file__))) +path.append(SITE_ROOT) + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "license_manager.settings.local") + + +application = get_wsgi_application() # pylint: disable=invalid-name diff --git a/manage.py b/manage.py new file mode 100755 index 00000000..c28acea2 --- /dev/null +++ b/manage.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python + +""" +Django administration utility. +""" + +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "license_manager.settings.local") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/openedx.yaml b/openedx.yaml new file mode 100644 index 00000000..004be0bf --- /dev/null +++ b/openedx.yaml @@ -0,0 +1,13 @@ +# This file describes this Open edX repo, as described in OEP-2: +# http://open-edx-proposals.readthedocs.io/en/latest/oeps/oep-0002.html#specification + +nick: licen +oeps: + oep-2: true # Repository metadata + oep-7: true # Python 3 + oep-30: true # PII annotation + oep-0018: true # Dependency management +openedx-release: {ref: master} +owner: georgebabey +supporting_teams: + - business-enterprise-team diff --git a/pylintrc b/pylintrc new file mode 100644 index 00000000..b1549a4e --- /dev/null +++ b/pylintrc @@ -0,0 +1,456 @@ +# *************************** +# ** DO NOT EDIT THIS FILE ** +# *************************** +# +# This file was generated by edx-lint: http://github.com/edx/edx-lint +# +# If you want to change this file, you have two choices, depending on whether +# you want to make a local change that applies only to this repo, or whether +# you want to make a central change that applies to all repos using edx-lint. +# +# LOCAL CHANGE: +# +# 1. Edit the local pylintrc_tweaks file to add changes just to this +# repo's file. +# +# 2. Run: +# +# $ edx_lint write pylintrc +# +# 3. This will modify the local file. Submit a pull request to get it +# checked in so that others will benefit. +# +# +# CENTRAL CHANGE: +# +# 1. Edit the pylintrc file in the edx-lint repo at +# https://github.com/edx/edx-lint/blob/master/edx_lint/files/pylintrc +# +# 2. install the updated version of edx-lint (in edx-lint): +# +# $ pip install . +# +# 3. Run (in edx-lint): +# +# # uses pylintrc_tweaks from edx-lint for linting in edx-lint +# # NOTE: Use Python 3.x, which no longer includes comments in the output file +# $ edx_lint write pylintrc +# +# 4. Make a new version of edx_lint, submit and review a pull request with the +# pylintrc update, and after merging, update the edx-lint version by +# creating a new tag in the repo (uses pbr). +# +# 5. In your local repo, install the newer version of edx-lint. +# +# 6. Run: +# +# # uses local pylintrc_tweaks +# $ edx_lint write pylintrc +# +# 7. This will modify the local file. Submit a pull request to get it +# checked in so that others will benefit. +# +# +# +# +# +# STAY AWAY FROM THIS FILE! +# +# +# +# +# +# SERIOUSLY. +# +# ------------------------------ +[MASTER] +ignore = ,migrations, settings, wsgi.py +persistent = yes +load-plugins = edx_lint.pylint,pylint_django,pylint_celery + +[MESSAGES CONTROL] +enable = + blacklisted-name, + line-too-long, + + syntax-error, + init-is-generator, + return-in-init, + function-redefined, + not-in-loop, + return-outside-function, + yield-outside-function, + return-arg-in-generator, + nonexistent-operator, + duplicate-argument-name, + abstract-class-instantiated, + bad-reversed-sequence, + continue-in-finally, + method-hidden, + access-member-before-definition, + no-method-argument, + no-self-argument, + invalid-slots-object, + assigning-non-slot, + invalid-slots, + inherit-non-class, + inconsistent-mro, + duplicate-bases, + non-iterator-returned, + unexpected-special-method-signature, + invalid-length-returned, + import-error, + used-before-assignment, + undefined-variable, + undefined-all-variable, + invalid-all-object, + no-name-in-module, + unbalance-tuple-unpacking, + unpacking-non-sequence, + bad-except-order, + raising-bad-type, + misplaced-bare-raise, + raising-non-exception, + nonimplemented-raised, + catching-non-exception, + slots-on-old-class, + super-on-old-class, + bad-super-call, + missing-super-argument, + no-member, + not-callable, + assignment-from-no-return, + no-value-for-parameter, + too-many-function-args, + unexpected-keyword-arg, + redundant-keyword-arg, + invalid-sequence-index, + invalid-slice-index, + assignment-from-none, + not-context-manager, + invalid-unary-operand-type, + unsupported-binary-operation, + repeated-keyword, + not-an-iterable, + not-a-mapping, + unsupported-membership-test, + unsubscriptable-object, + logging-unsupported-format, + logging-too-many-args, + logging-too-few-args, + bad-format-character, + truncated-format-string, + mixed-fomat-string, + format-needs-mapping, + missing-format-string-key, + too-many-format-args, + too-few-format-args, + bad-str-strip-call, + model-unicode-not-callable, + super-method-not-called, + non-parent-method-called, + test-inherits-tests, + translation-of-non-string, + redefined-variable-type, + cyclical-import, + unreachable, + dangerous-default-value, + pointless-statement, + pointless-string-statement, + expression-not-assigned, + duplicate-key, + confusing-with-statement, + using-constant-test, + lost-exception, + assert-on-tuple, + attribute-defined-outside-init, + bad-staticmethod-argument, + arguments-differ, + signature-differs, + abstract-method, + super-init-not-called, + relative-import, + import-self, + misplaced-future, + invalid-encoded-data, + global-variable-undefined, + redefined-outer-name, + redefined-builtin, + redefined-in-handler, + undefined-loop-variable, + cell-var-from-loop, + duplicate-except, + nonstandard-exception, + binary-op-exception, + property-on-old-class, + bad-format-string-key, + unused-format-string-key, + bad-format-string, + missing-format-argument-key, + unused-format-string-argument, + format-combined-specification, + missing-format-attribute, + invalid-format-index, + anomalous-backslash-in-string, + anomalous-unicode-escape-in-string, + bad-open-mode, + boolean-datetime, + + fatal, + astroid-error, + parse-error, + method-check-failed, + django-not-available, + raw-checker-failed, + django-not-available-placeholder, + + empty-docstring, + invalid-characters-in-docstring, + missing-docstring, + wrong-spelling-in-comment, + wrong-spelling-in-docstring, + + unused-import, + unused-variable, + unused-argument, + + exec-used, + eval-used, + + bad-classmethod-argument, + bad-mcs-classmethod-argument, + bad-mcs-method-argument, + bad-whitespace, + consider-iterating-dictionary, + consider-using-enumerate, + literal-used-as-attribute, + multiple-imports, + multiple-statements, + old-style-class, + simplifiable-range, + singleton-comparison, + superfluous-parens, + unidiomatic-typecheck, + unneeded-not, + wrong-assert-type, + simplifiable-if-statement, + no-classmethod-decorator, + no-staticmethod-decorator, + unnecessary-pass, + unnecessary-lambda, + useless-else-on-loop, + unnecessary-semicolon, + reimported, + global-variable-not-assigned, + global-at-module-level, + bare-except, + broad-except, + logging-not-lazy, + redundant-unittest-assert, + model-missing-unicode, + model-has-unicode, + model-no-explicit-unicode, + protected-access, + + deprecated-module, + deprecated-method, + + too-many-nested-blocks, + too-many-statements, + too-many-boolean-expressions, + + wrong-import-order, + wrong-import-position, + wildcard-import, + + missing-final-newline, + mixed-line-endings, + trailing-newlines, + trailing-whitespace, + unexpected-line-ending-format, + mixed-indentation, + + bad-option-value, + unrecognized-inline-option, + useless-suppression, + bad-inline-option, + deprecated-pragma, +disable = + bad-continuation, + invalid-name, + misplaced-comparison-constant, + file-ignored, + bad-indentation, + lowercase-l-suffix, + unused-wildcard-import, + global-statement, + no-else-return, + + apply-builtin, + backtick, + basestring-builtin, + buffer-builtin, + cmp-builtin, + cmp-method, + coerce-builtin, + coerce-method, + delslice-method, + dict-iter-method, + dict-view-method, + duplicate-code, + execfile-builtin, + feature-toggle-needs-doc, + file-builtin, + filter-builtin-not-iterating, + fixme, + getslice-method, + hex-method, + illegal-waffle-usage, + import-star-module-level, + indexing-exception, + input-builtin, + intern-builtin, + locally-disabled, + locally-enabled, + logging-format-interpolation, + long-builtin, + long-suffix, + map-builtin-not-iterating, + metaclass-assignment, + next-method-called, + no-absolute-import, + no-init, + no-self-use, + nonzero-method, + oct-method, + old-division, + old-ne-operator, + old-octal-literal, + old-raise-syntax, + parameter-unpacking, + print-statement, + raising-string, + range-builtin-not-iterating, + raw_input-builtin, + reduce-builtin, + reload-builtin, + round-builtin, + setslice-method, + standarderror-builtin, + suppressed-message, + too-few-public-methods, + too-many-ancestors, + too-many-arguments, + too-many-branches, + too-many-instance-attributes, + too-many-lines, + too-many-locals, + too-many-public-methods, + too-many-return-statements, + ungrouped-imports, + unichr-builtin, + unicode-builtin, + unpacking-in-except, + using-cmp-argument, + xrange-builtin, + zip-builtin-not-iterating,,invalid-name + +[REPORTS] +output-format = text +files-output = no +reports = no +evaluation = 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +[BASIC] +bad-functions = map,filter,apply,input +module-rgx = (([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ +const-rgx = (([A-Z_][A-Z0-9_]*)|(__.*__)|log|urlpatterns|logger|User)$ +class-rgx = [A-Z_][a-zA-Z0-9]+$ +function-rgx = ([a-z_][a-z0-9_]{2,40}|test_[a-z0-9_]+)$ +method-rgx = ([a-z_][a-z0-9_]{2,40}|setUp|set[Uu]pClass|tearDown|tear[Dd]ownClass|assert[A-Z]\w*|maxDiff|test_[a-z0-9_]+)$ +attr-rgx = [a-z_][a-z0-9_]{2,30}$ +argument-rgx = [a-z_][a-z0-9_]{2,30}$ +variable-rgx = [a-z_][a-z0-9_]{2,30}$ +class-attribute-rgx = ([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ +inlinevar-rgx = [A-Za-z_][A-Za-z0-9_]*$ +good-names = f,i,j,k,db,ex,Run,_,__ +bad-names = foo,bar,baz,toto,tutu,tata +no-docstring-rgx = __.*__$|test_.+|setUp$|setUpClass$|tearDown$|tearDownClass$|Meta$ +docstring-min-length = 5 + +[FORMAT] +max-line-length = 120 +ignore-long-lines = ^\s*(# )?((?)|(\.\. \w+: .*))$ +single-line-if-stmt = no +no-space-check = trailing-comma,dict-separator +max-module-lines = 1000 +indent-string = ' ' + +[MISCELLANEOUS] +notes = FIXME,XXX,TODO + +[SIMILARITIES] +min-similarity-lines = 4 +ignore-comments = yes +ignore-docstrings = yes +ignore-imports = no + +[TYPECHECK] +ignore-mixin-members = yes +ignored-classes = SQLObject +unsafe-load-any-extension = yes +generated-members = + REQUEST, + acl_users, + aq_parent, + objects, + DoesNotExist, + can_read, + can_write, + get_url, + size, + content, + status_code, + create, + build, + fields, + tag, + org, + course, + category, + name, + revision, + _meta, + +[VARIABLES] +init-import = no +dummy-variables-rgx = _|dummy|unused|.*_unused +additional-builtins = + +[CLASSES] +defining-attr-methods = __init__,__new__,setUp +valid-classmethod-first-arg = cls +valid-metaclass-classmethod-first-arg = mcs + +[DESIGN] +max-args = 5 +ignored-argument-names = _.* +max-locals = 15 +max-returns = 6 +max-branches = 12 +max-statements = 50 +max-parents = 7 +max-attributes = 7 +min-public-methods = 2 +max-public-methods = 20 + +[IMPORTS] +deprecated-modules = regsub,TERMIOS,Bastion,rexec +import-graph = +ext-import-graph = +int-import-graph = + +[EXCEPTIONS] +overgeneral-exceptions = Exception + +# efb9c8bedea8d8fa6819baa386a6f8cdac569dda diff --git a/pylintrc_tweaks b/pylintrc_tweaks new file mode 100644 index 00000000..c9531310 --- /dev/null +++ b/pylintrc_tweaks @@ -0,0 +1,8 @@ +[MASTER] +ignore+= ,migrations, settings, wsgi.py + +[BASIC] +const-rgx = (([A-Z_][A-Z0-9_]*)|(__.*__)|log|urlpatterns|logger|User)$ + +[MESSAGES CONTROL] +DISABLE+= ,invalid-name diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..2e116ca8 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,10 @@ +[pytest] +DJANGO_SETTINGS_MODULE = license_manager.settings.test +addopts = --cov license_manager --cov-report term-missing --cov-report xml +norecursedirs = .* docs requirements + +# Filter depr warnings coming from packages that we can't control. +filterwarnings = + ignore:.*urlresolvers is deprecated in favor of.*:DeprecationWarning:auth_backends.views:5 + ignore:.*invalid escape sequence.*:DeprecationWarning:.*(newrelic|uritemplate|psutil).* + ignore:.*the imp module is deprecated in favour of importlib.*:DeprecationWarning:.*distutils.* diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..d1197135 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +# This file is here because many Platforms as a Service look for +# requirements.txt in the root directory of a project. +-r requirements/production.txt diff --git a/requirements/base.in b/requirements/base.in new file mode 100644 index 00000000..f14069ea --- /dev/null +++ b/requirements/base.in @@ -0,0 +1,13 @@ +# Core requirements for using this application + +Django>=2.2,<2.3 # Web application framework +django-cors-headers +django-extensions +django-rest-swagger +django-waffle +djangorestframework +edx-auth-backends +edx-django-utils +edx-drf-extensions +edx-rest-api-client==1.9.2 +pytz diff --git a/requirements/base.txt b/requirements/base.txt new file mode 100644 index 00000000..82dc5615 --- /dev/null +++ b/requirements/base.txt @@ -0,0 +1,56 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# make upgrade +# +certifi==2020.4.5.1 # via requests +cffi==1.14.0 # via cryptography +chardet==3.0.4 # via requests +coreapi==2.3.3 # via django-rest-swagger, openapi-codec +coreschema==0.0.4 # via coreapi +cryptography==2.9 # via social-auth-core +defusedxml==0.6.0 # via python3-openid, social-auth-core +django-cors-headers==3.2.1 +django-extensions==2.2.9 +django-rest-swagger==2.2.0 +django-waffle==0.20.0 +django==2.2.12 +djangorestframework==3.11.0 +drf-jwt==1.14.0 # via edx-drf-extensions +edx-auth-backends==3.0.2 +edx-django-utils==3.1 +edx-drf-extensions==5.0.2 +edx-opaque-keys==2.0.2 # via edx-drf-extensions +edx-rest-api-client==1.9.2 +future==0.18.2 # via pyjwkest +idna==2.9 # via requests +itypes==1.1.0 # via coreapi +jinja2==2.11.1 # via coreschema +markupsafe==1.1.1 # via jinja2 +newrelic==5.10.0.138 # via edx-django-utils +oauthlib==3.1.0 # via requests-oauthlib, social-auth-core +openapi-codec==1.3.2 # via django-rest-swagger +pbr==5.4.5 # via stevedore +psutil==1.2.1 # via edx-django-utils +pycparser==2.20 # via cffi +pycryptodomex==3.9.7 # via pyjwkest +pyjwkest==1.4.2 # via edx-drf-extensions +pyjwt==1.7.1 # via drf-jwt, edx-auth-backends, edx-rest-api-client, social-auth-core +pymongo==3.10.1 # via edx-opaque-keys +python-dateutil==2.8.1 # via edx-drf-extensions +python3-openid==3.1.0 # via social-auth-core +pytz==2019.3 +requests-oauthlib==1.3.0 # via social-auth-core +requests==2.23.0 # via coreapi, edx-drf-extensions, edx-rest-api-client, pyjwkest, requests-oauthlib, slumber, social-auth-core +rest-condition==1.0.3 # via edx-drf-extensions +semantic-version==2.8.4 # via edx-drf-extensions +simplejson==3.17.0 # via django-rest-swagger +six==1.14.0 # via cryptography, django-extensions, django-waffle, edx-auth-backends, edx-drf-extensions, edx-opaque-keys, pyjwkest, python-dateutil, social-auth-app-django, social-auth-core, stevedore +slumber==0.7.1 # via edx-rest-api-client +social-auth-app-django==3.1.0 # via edx-auth-backends +social-auth-core==3.3.3 # via edx-auth-backends, social-auth-app-django +sqlparse==0.3.1 # via django +stevedore==1.32.0 # via edx-opaque-keys +uritemplate==3.0.1 # via coreapi +urllib3==1.25.8 # via requests diff --git a/requirements/dev.in b/requirements/dev.in new file mode 100644 index 00000000..c3a65da5 --- /dev/null +++ b/requirements/dev.in @@ -0,0 +1,9 @@ +# Additional requirements for development of this application + +-r pip-tools.txt # pip-tools and its deps, for managing requirements files +-r validation.txt # Core, testing, and quality check dependencies + +diff-cover # Changeset diff test coverage +edx-i18n-tools # For i18n_tool dummy +django-debug-toolbar # For debugging Django +#transifex-client # For managing translations diff --git a/requirements/doc.in b/requirements/doc.in new file mode 100644 index 00000000..b33df915 --- /dev/null +++ b/requirements/doc.in @@ -0,0 +1,8 @@ +# Requirements for documentation validation + +-r test.txt # Core and testing dependencies for this package + +doc8 # reStructuredText style checker +edx_sphinx_theme # edX theme for Sphinx output +readme_renderer # Validates README.rst for usage on PyPI +Sphinx # Documentation builder diff --git a/requirements/doc.txt b/requirements/doc.txt new file mode 100644 index 00000000..327a6c4f --- /dev/null +++ b/requirements/doc.txt @@ -0,0 +1,110 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# make upgrade +# +alabaster==0.7.12 # via sphinx +astroid==2.3.3 +attrs==19.3.0 +babel==2.8.0 # via sphinx +bleach==3.1.4 # via readme-renderer +certifi==2020.4.5.1 +cffi==1.14.0 +chardet==3.0.4 +click-log==0.3.2 +click==7.1.1 +code-annotations==0.3.3 +coreapi==2.3.3 +coreschema==0.0.4 +coverage==5.0.4 +cryptography==2.9 +defusedxml==0.6.0 +django-cors-headers==3.2.1 +django-dynamic-fixture==3.1.0 +django-extensions==2.2.9 +django-rest-swagger==2.2.0 +django-waffle==0.20.0 +django==2.2.12 +djangorestframework==3.11.0 +doc8==0.8.0 +docutils==0.16 # via doc8, readme-renderer, restructuredtext-lint, sphinx +drf-jwt==1.14.0 +edx-auth-backends==3.0.2 +edx-django-utils==3.1 +edx-drf-extensions==5.0.2 +edx-lint==1.4.1 +edx-opaque-keys==2.0.2 +edx-rest-api-client==1.9.2 +edx-sphinx-theme==1.5.0 +future==0.18.2 +idna==2.9 +imagesize==1.2.0 # via sphinx +importlib-metadata==1.6.0 +isort==4.3.21 +itypes==1.1.0 +jinja2==2.11.1 +lazy-object-proxy==1.4.3 +markupsafe==1.1.1 +mccabe==0.6.1 +mock==4.0.2 +more-itertools==8.2.0 +newrelic==5.10.0.138 +oauthlib==3.1.0 +openapi-codec==1.3.2 +packaging==20.3 +pbr==5.4.5 +pluggy==0.13.1 +psutil==1.2.1 +py==1.8.1 +pycparser==2.20 +pycryptodomex==3.9.7 +pygments==2.6.1 # via readme-renderer, sphinx +pyjwkest==1.4.2 +pyjwt==1.7.1 +pylint-celery==0.3 +pylint-django==2.0.11 +pylint-plugin-utils==0.6 +pylint==2.4.2 +pymongo==3.10.1 +pyparsing==2.4.7 +pytest-cov==2.8.1 +pytest-django==3.9.0 +pytest==5.4.1 +python-dateutil==2.8.1 +python-slugify==4.0.0 +python3-openid==3.1.0 +pytz==2019.3 +pyyaml==5.3.1 +readme-renderer==25.0 +requests-oauthlib==1.3.0 +requests==2.23.0 +rest-condition==1.0.3 +restructuredtext-lint==1.3.0 # via doc8 +semantic-version==2.8.4 +simplejson==3.17.0 +six==1.14.0 +slumber==0.7.1 +snowballstemmer==2.0.0 # via sphinx +social-auth-app-django==3.1.0 +social-auth-core==3.3.3 +sphinx==3.0.0 +sphinxcontrib-applehelp==1.0.2 # via sphinx +sphinxcontrib-devhelp==1.0.2 # via sphinx +sphinxcontrib-htmlhelp==1.0.3 # via sphinx +sphinxcontrib-jsmath==1.0.1 # via sphinx +sphinxcontrib-qthelp==1.0.3 # via sphinx +sphinxcontrib-serializinghtml==1.1.4 # via sphinx +sqlparse==0.3.1 +stevedore==1.32.0 +text-unidecode==1.3 +typed-ast==1.4.1 +uritemplate==3.0.1 +urllib3==1.25.8 +wcwidth==0.1.9 +webencodings==0.5.1 # via bleach +wrapt==1.11.2 +zipp==3.1.0 + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/requirements/monitoring/requirements.txt b/requirements/monitoring/requirements.txt new file mode 100644 index 00000000..22f12486 --- /dev/null +++ b/requirements/monitoring/requirements.txt @@ -0,0 +1,9 @@ +# This requirements.txt file is meant to pull in other requirements.txt files so that +# dependency monitoring tools like gemnasium.com can easily process them. + +# It can not be used to do the full installation because it is not in the correct +# order. + +-r ../dev.txt # Includes validation requirements +-r ../optional.txt +-r ../production.txt diff --git a/requirements/optional.txt b/requirements/optional.txt new file mode 100644 index 00000000..9870eb7d --- /dev/null +++ b/requirements/optional.txt @@ -0,0 +1 @@ +newrelic diff --git a/requirements/pip-tools.in b/requirements/pip-tools.in new file mode 100644 index 00000000..c17d0b41 --- /dev/null +++ b/requirements/pip-tools.in @@ -0,0 +1,3 @@ +# Just the dependencies to run pip-tools, mainly for the "upgrade" make target + +pip-tools # Contains pip-compile, used to generate pip requirements files diff --git a/requirements/pip-tools.txt b/requirements/pip-tools.txt new file mode 100644 index 00000000..9e9b1940 --- /dev/null +++ b/requirements/pip-tools.txt @@ -0,0 +1,9 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# make upgrade +# +click==7.1.1 # via pip-tools +pip-tools==4.5.1 +six==1.14.0 # via pip-tools diff --git a/requirements/private.readme b/requirements/private.readme new file mode 100644 index 00000000..5600a107 --- /dev/null +++ b/requirements/private.readme @@ -0,0 +1,15 @@ +# If there are any Python packages you want to keep in your virtualenv beyond +# those listed in the official requirements files, create a "private.in" file +# and list them there. Generate the corresponding "private.txt" file pinning +# all of their indirect dependencies to specific versions as follows: + +# pip-compile private.in + +# This allows you to use "pip-sync" without removing these packages: + +# pip-sync requirements/*.txt + +# "private.in" and "private.txt" aren't checked into git to avoid merge +# conflicts, and the presence of this file allows "private.*" to be +# included in scripted pip-sync usage without requiring that those files be +# created first. diff --git a/requirements/production.in b/requirements/production.in new file mode 100644 index 00000000..5f10738e --- /dev/null +++ b/requirements/production.in @@ -0,0 +1,7 @@ +# Packages required in a production environment +-r base.txt + +gevent +gunicorn +#mysqlclient +PyYAML>=5.1 diff --git a/requirements/quality.in b/requirements/quality.in new file mode 100644 index 00000000..257eac84 --- /dev/null +++ b/requirements/quality.in @@ -0,0 +1,9 @@ +# Requirements for code quality checks + +-r base.txt # Core dependencies for this package + +caniusepython3 # Additional Python 3 compatibility pylint checks +edx-lint # edX pylint rules and plugins +isort # to standardize order of imports +pycodestyle # PEP 8 compliance validation +pydocstyle # PEP 257 compliance validation diff --git a/requirements/quality.txt b/requirements/quality.txt new file mode 100644 index 00000000..3e6fd52c --- /dev/null +++ b/requirements/quality.txt @@ -0,0 +1,81 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# make upgrade +# +argparse==1.4.0 # via caniusepython3 +astroid==2.3.3 # via pylint, pylint-celery +backports.functools-lru-cache==1.6.1 # via caniusepython3 +caniusepython3==7.2.0 +certifi==2020.4.5.1 +cffi==1.14.0 +chardet==3.0.4 +click-log==0.3.2 # via edx-lint +click==7.1.1 # via click-log, edx-lint +coreapi==2.3.3 +coreschema==0.0.4 +cryptography==2.9 +defusedxml==0.6.0 +distlib==0.3.0 # via caniusepython3 +django-cors-headers==3.2.1 +django-extensions==2.2.9 +django-rest-swagger==2.2.0 +django-waffle==0.20.0 +django==2.2.12 +djangorestframework==3.11.0 +drf-jwt==1.14.0 +edx-auth-backends==3.0.2 +edx-django-utils==3.1 +edx-drf-extensions==5.0.2 +edx-lint==1.4.1 +edx-opaque-keys==2.0.2 +edx-rest-api-client==1.9.2 +future==0.18.2 +idna==2.9 +isort==4.3.21 +itypes==1.1.0 +jinja2==2.11.1 +lazy-object-proxy==1.4.3 # via astroid +markupsafe==1.1.1 +mccabe==0.6.1 # via pylint +newrelic==5.10.0.138 +oauthlib==3.1.0 +openapi-codec==1.3.2 +packaging==20.3 # via caniusepython3 +pbr==5.4.5 +psutil==1.2.1 +pycodestyle==2.5.0 +pycparser==2.20 +pycryptodomex==3.9.7 +pydocstyle==5.0.2 +pyjwkest==1.4.2 +pyjwt==1.7.1 +pylint-celery==0.3 # via edx-lint +pylint-django==2.0.11 # via edx-lint +pylint-plugin-utils==0.6 # via pylint-celery, pylint-django +pylint==2.4.2 # via edx-lint, pylint-celery, pylint-django, pylint-plugin-utils +pymongo==3.10.1 +pyparsing==2.4.7 # via packaging +python-dateutil==2.8.1 +python3-openid==3.1.0 +pytz==2019.3 +requests-oauthlib==1.3.0 +requests==2.23.0 +rest-condition==1.0.3 +semantic-version==2.8.4 +simplejson==3.17.0 +six==1.14.0 +slumber==0.7.1 +snowballstemmer==2.0.0 # via pydocstyle +social-auth-app-django==3.1.0 +social-auth-core==3.3.3 +sqlparse==0.3.1 +stevedore==1.32.0 +typed-ast==1.4.1 # via astroid +uritemplate==3.0.1 +urllib3==1.25.8 +wrapt==1.11.2 # via astroid + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/requirements/test.in b/requirements/test.in new file mode 100644 index 00000000..b5e38f74 --- /dev/null +++ b/requirements/test.in @@ -0,0 +1,11 @@ +# Requirements for test runs. + +-r base.txt # Core dependencies for this package + +code-annotations +coverage +django-dynamic-fixture # library to create dynamic model instances for testing purposes +edx-lint +mock +pytest-cov +pytest-django diff --git a/requirements/test.txt b/requirements/test.txt new file mode 100644 index 00000000..f455f509 --- /dev/null +++ b/requirements/test.txt @@ -0,0 +1,88 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# make upgrade +# +astroid==2.3.3 # via pylint, pylint-celery +attrs==19.3.0 # via pytest +certifi==2020.4.5.1 +cffi==1.14.0 +chardet==3.0.4 +click-log==0.3.2 # via edx-lint +click==7.1.1 # via click-log, code-annotations, edx-lint +code-annotations==0.3.3 +coreapi==2.3.3 +coreschema==0.0.4 +coverage==5.0.4 +cryptography==2.9 +defusedxml==0.6.0 +django-cors-headers==3.2.1 +django-dynamic-fixture==3.1.0 +django-extensions==2.2.9 +django-rest-swagger==2.2.0 +django-waffle==0.20.0 +django==2.2.12 +djangorestframework==3.11.0 +drf-jwt==1.14.0 +edx-auth-backends==3.0.2 +edx-django-utils==3.1 +edx-drf-extensions==5.0.2 +edx-lint==1.4.1 +edx-opaque-keys==2.0.2 +edx-rest-api-client==1.9.2 +future==0.18.2 +idna==2.9 +importlib-metadata==1.6.0 # via pluggy, pytest +isort==4.3.21 # via pylint +itypes==1.1.0 +jinja2==2.11.1 +lazy-object-proxy==1.4.3 # via astroid +markupsafe==1.1.1 +mccabe==0.6.1 # via pylint +mock==4.0.2 +more-itertools==8.2.0 # via pytest +newrelic==5.10.0.138 +oauthlib==3.1.0 +openapi-codec==1.3.2 +packaging==20.3 # via pytest +pbr==5.4.5 +pluggy==0.13.1 # via pytest +psutil==1.2.1 +py==1.8.1 # via pytest +pycparser==2.20 +pycryptodomex==3.9.7 +pyjwkest==1.4.2 +pyjwt==1.7.1 +pylint-celery==0.3 # via edx-lint +pylint-django==2.0.11 # via edx-lint +pylint-plugin-utils==0.6 # via pylint-celery, pylint-django +pylint==2.4.2 # via edx-lint, pylint-celery, pylint-django, pylint-plugin-utils +pymongo==3.10.1 +pyparsing==2.4.7 # via packaging +pytest-cov==2.8.1 +pytest-django==3.9.0 +pytest==5.4.1 # via pytest-cov, pytest-django +python-dateutil==2.8.1 +python-slugify==4.0.0 # via code-annotations +python3-openid==3.1.0 +pytz==2019.3 +pyyaml==5.3.1 # via code-annotations +requests-oauthlib==1.3.0 +requests==2.23.0 +rest-condition==1.0.3 +semantic-version==2.8.4 +simplejson==3.17.0 +six==1.14.0 +slumber==0.7.1 +social-auth-app-django==3.1.0 +social-auth-core==3.3.3 +sqlparse==0.3.1 +stevedore==1.32.0 +text-unidecode==1.3 # via python-slugify +typed-ast==1.4.1 # via astroid +uritemplate==3.0.1 +urllib3==1.25.8 +wcwidth==0.1.9 # via pytest +wrapt==1.11.2 # via astroid +zipp==3.1.0 # via importlib-metadata diff --git a/requirements/validation.in b/requirements/validation.in new file mode 100644 index 00000000..cf367783 --- /dev/null +++ b/requirements/validation.in @@ -0,0 +1,4 @@ +# Requirements for validation (testing, code quality). + +-r quality.txt +-r test.txt diff --git a/requirements/validation.txt b/requirements/validation.txt new file mode 100644 index 00000000..be84f536 --- /dev/null +++ b/requirements/validation.txt @@ -0,0 +1,98 @@ +# +# This file is autogenerated by pip-compile +# To update, run: +# +# make upgrade +# +argparse==1.4.0 +astroid==2.3.3 +attrs==19.3.0 +backports.functools-lru-cache==1.6.1 +caniusepython3==7.2.0 +certifi==2020.4.5.1 +cffi==1.14.0 +chardet==3.0.4 +click-log==0.3.2 +click==7.1.1 +code-annotations==0.3.3 +coreapi==2.3.3 +coreschema==0.0.4 +coverage==5.0.4 +cryptography==2.9 +defusedxml==0.6.0 +distlib==0.3.0 +django-cors-headers==3.2.1 +django-dynamic-fixture==3.1.0 +django-extensions==2.2.9 +django-rest-swagger==2.2.0 +django-waffle==0.20.0 +django==2.2.12 +djangorestframework==3.11.0 +drf-jwt==1.14.0 +edx-auth-backends==3.0.2 +edx-django-utils==3.1 +edx-drf-extensions==5.0.2 +edx-lint==1.4.1 +edx-opaque-keys==2.0.2 +edx-rest-api-client==1.9.2 +future==0.18.2 +idna==2.9 +importlib-metadata==1.6.0 +isort==4.3.21 +itypes==1.1.0 +jinja2==2.11.1 +lazy-object-proxy==1.4.3 +markupsafe==1.1.1 +mccabe==0.6.1 +mock==4.0.2 +more-itertools==8.2.0 +newrelic==5.10.0.138 +oauthlib==3.1.0 +openapi-codec==1.3.2 +packaging==20.3 +pbr==5.4.5 +pluggy==0.13.1 +psutil==1.2.1 +py==1.8.1 +pycodestyle==2.5.0 +pycparser==2.20 +pycryptodomex==3.9.7 +pydocstyle==5.0.2 +pyjwkest==1.4.2 +pyjwt==1.7.1 +pylint-celery==0.3 +pylint-django==2.0.11 +pylint-plugin-utils==0.6 +pylint==2.4.2 +pymongo==3.10.1 +pyparsing==2.4.7 +pytest-cov==2.8.1 +pytest-django==3.9.0 +pytest==5.4.1 +python-dateutil==2.8.1 +python-slugify==4.0.0 +python3-openid==3.1.0 +pytz==2019.3 +pyyaml==5.3.1 +requests-oauthlib==1.3.0 +requests==2.23.0 +rest-condition==1.0.3 +semantic-version==2.8.4 +simplejson==3.17.0 +six==1.14.0 +slumber==0.7.1 +snowballstemmer==2.0.0 +social-auth-app-django==3.1.0 +social-auth-core==3.3.3 +sqlparse==0.3.1 +stevedore==1.32.0 +text-unidecode==1.3 +typed-ast==1.4.1 +uritemplate==3.0.1 +urllib3==1.25.8 +wcwidth==0.1.9 +wrapt==1.11.2 +zipp==3.1.0 + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..2242d7b2 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,14 @@ +[pycodestyle] +ignore=E501 +max-line-length = 120 +exclude=.git,settings,migrations,license_manager/static,bower_components,license_manager/wsgi.py + +[tool:isort] +indent=' ' +line_length=80 +multi_line_output=3 +lines_after_imports=2 +include_trailing_comma=True +skip= + settings + migrations