diff --git a/.annotation_safe_list.yml b/.annotation_safe_list.yml index 62eaaa7..a8ef6e3 100644 --- a/.annotation_safe_list.yml +++ b/.annotation_safe_list.yml @@ -3,39 +3,39 @@ # See https://code-annotations.readthedocs.io/en/latest/safelist.html for more information. # # fake_app_1.FakeModelName: -# ".. no_pii:": "This model has no PII" +# ".. 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" + ".. no_pii:": This model has no PII auth.Group: - ".. no_pii:": "This model has no PII" + ".. no_pii:": This model has no PII auth.Permission: - ".. no_pii:": "This model has no PII" + ".. no_pii:": This model has no PII auth.User: - ".. pii": "This model minimally contains a username, password, and email" - ".. pii_types": "username, email_address, password" - ".. pii_retirement": "consumer_api" + ".. pii": This model minimally contains a username, password, and email + ".. pii_types": username, email_address, password + ".. pii_retirement": consumer_api contenttypes.ContentType: - ".. no_pii:": "This model has no PII" + ".. no_pii:": This model has no PII sessions.Session: - ".. no_pii:": "This model has no PII" + ".. no_pii:": This model has no PII social_django.Association: - ".. no_pii:": "This model has no PII" + ".. no_pii:": This model has no PII social_django.Code: - ".. pii:": "Email address" + ".. pii:": Email address ".. pii_types:": other ".. pii_retirement:": local_api social_django.Nonce: - ".. no_pii:": "This model has no PII" + ".. no_pii:": This model has no PII social_django.Partial: - ".. no_pii:": "This model has no PII" + ".. no_pii:": This model has no PII social_django.UserSocialAuth: - ".. no_pii:": "This model has no PII" + ".. no_pii:": This model has no PII waffle.Flag: - ".. no_pii:": "This model has no PII" + ".. no_pii:": This model has no PII waffle.Sample: - ".. no_pii:": "This model has no PII" + ".. no_pii:": This model has no PII waffle.Switch: - ".. no_pii:": "This model has no PII" + ".. no_pii:": This model has no PII diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3fb61de..b2f2733 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,7 @@ on: branches: [main] pull_request: branches: - - '**' + - '**' jobs: @@ -16,31 +16,31 @@ jobs: matrix: os: [ubuntu-latest] python-version: [3.11, 3.12] - toxenv: [django42, django52, quality, docs, pii_check] + toxenv: [django52, quality, docs, pii_check] steps: - - uses: actions/checkout@v3 - - name: setup python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Install pip - run: pip install -r requirements/pip.txt - - - name: Install Dependencies - run: pip install -r requirements/ci.txt - - - name: Run Tests - env: - TOXENV: ${{ matrix.toxenv }} - run: tox - - - name: Run coverage - if: matrix.python-version == '3.11' && matrix.toxenv == 'django42' - uses: codecov/codecov-action@v3 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - with: - flags: unittests - fail_ci_if_error: true + - uses: actions/checkout@v3 + - name: setup python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install pip + run: pip install -r requirements/pip.txt + + - name: Install Dependencies + run: pip install -r requirements/ci.txt + + - name: Run Tests + env: + TOXENV: ${{ matrix.toxenv }} + run: tox + + - name: Run coverage + if: matrix.python-version == '3.11' && matrix.toxenv == 'django52' + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + flags: unittests + fail_ci_if_error: true diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 8e36925..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Lint - -on: - push: - branches: [main] - pull_request: - branches: - - '**' - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: psf/black@stable - with: - options: --check --diff diff --git a/.pii_annotations.yml b/.pii_annotations.yml index 7da8f3c..d224255 100644 --- a/.pii_annotations.yml +++ b/.pii_annotations.yml @@ -7,29 +7,29 @@ annotations: "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 + 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 + 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/.readthedocs.yaml b/.readthedocs.yaml index ce8bde2..c872d00 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -12,9 +12,9 @@ sphinx: # Set the version of python needed to build these docs. build: - os: "ubuntu-22.04" + os: ubuntu-lts-latest tools: - python: "3.8" + python: "3.11" python: install: diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..7b86ffe --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,23 @@ +extends: default + +ignore: | + .pytest_cache/ + .ruff_cache/ + .tox/ + pii_report/ + venv/ + .venv/ +rules: + indentation: + spaces: consistent + check-multi-line-strings: true + line-length: disable + document-start: disable + quoted-strings: + quote-type: any + required: only-when-needed + braces: + forbid: non-empty + level: warning + truthy: + check-keys: false diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1b70094..e4b8948 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,20 @@ Unreleased * +0.4.0 - 2026-06-12 +****************** + +Fixed +===== + +* Mark course key admin widget as safe (Django 5.2 automatically escapes this code). +* Do not evaluate DB queries when building the docs. + +Removed +======= + +* Django 4.2 support. + 0.3.5 - 2025-09-01 ****************** diff --git a/Makefile b/Makefile index b30a6b0..2c1b2a8 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ upgrade: ## update the requirements/*.txt files with the latest packages satisfy sed '/^[dD]jango==/d' requirements/test.txt > requirements/test.tmp mv requirements/test.tmp requirements/test.txt -quality: ## check coding style with pycodestyle and pylint +quality: ## check coding style with pycodestyle, pylint, ruff and yamllint tox -e quality pii_check: ## check for PII annotations on all Django models diff --git a/catalog-info.yaml b/catalog-info.yaml index d6909a4..245a558 100644 --- a/catalog-info.yaml +++ b/catalog-info.yaml @@ -4,10 +4,14 @@ apiVersion: backstage.io/v1alpha1 kind: "" metadata: - name: 'learning-paths-plugin' - description: "Learning Paths plugin" + name: learning-paths-plugin + description: Learning Paths plugin annotations: # (Optional) Annotation keys and values can be whatever you want. + # We use it in Open edX repos to have a comma-separated list of GitHub user + # names that might be interested in changes to the architecture of this + # component. + openedx.org/arch-interest-groups: "" spec: # (Required) This can be a group(`group:` or a user(`user:`) @@ -17,12 +21,12 @@ spec: type: '' # (Required) Acceptable Lifecycle Values: experimental, production, deprecated - lifecycle: 'experimental' + lifecycle: experimental # (Optional) The value can be the name of any known component. - subcomponentOf: '' + subcomponentOf: # (Optional) An array of different components or resources. dependsOn: - - '' - - '' + - + - diff --git a/codecov.yml b/codecov.yml index 5ccf75a..e2f06bf 100644 --- a/codecov.yml +++ b/codecov.yml @@ -2,11 +2,11 @@ coverage: status: project: default: - enabled: yes + enabled: true target: 85% patch: default: - enabled: yes + enabled: true target: 90% comment: false diff --git a/docs/conf.py b/docs/conf.py index b0b9c23..7e206cd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -518,8 +518,8 @@ def get_version(*file_paths): intersphinx_mapping = { "python": ("https://docs.python.org/3.11", None), "django": ( - "https://docs.djangoproject.com/en/4.2/", - "https://docs.djangoproject.com/en/4.2/_objects/", + "https://docs.djangoproject.com/en/5.2/", + "https://docs.djangoproject.com/en/5.2/_objects/", ), "model_utils": ("https://django-model-utils.readthedocs.io/en/latest/", None), } @@ -550,7 +550,23 @@ def on_init(app): # pylint: disable=unused-argument ) +def skip_lazy_db_attributes(app, what, name, obj, skip, options): # pylint: disable=unused-argument + """ + Skip documenting attributes backed by a lazy database query. + + Sphinx autodoc calls ``repr()`` on every documented attribute. For class attributes, such as a DRF + view's ``queryset = Model.objects.all()``, this forces the lazy QuerySet to hit the database, which fails + during the docs build because no tables exist. Their runtime value is not useful documentation anyway. + """ + from django.db.models import Manager, QuerySet # pylint: disable=import-outside-toplevel + + if isinstance(obj, (QuerySet, Manager)): + return True + return skip + + def setup(app): """Sphinx extension: run sphinx-apidoc.""" event = "builder-inited" app.connect(event, on_init) + app.connect("autodoc-skip-member", skip_lazy_db_attributes) diff --git a/learning_paths/__init__.py b/learning_paths/__init__.py index f90c958..efc2392 100644 --- a/learning_paths/__init__.py +++ b/learning_paths/__init__.py @@ -2,4 +2,4 @@ Learning Paths plugin. """ -__version__ = "0.3.5" +__version__ = "0.4.0" diff --git a/learning_paths/admin.py b/learning_paths/admin.py index 13f0dfe..86ec2d7 100644 --- a/learning_paths/admin.py +++ b/learning_paths/admin.py @@ -11,6 +11,7 @@ from django.db import transaction from django.http import HttpResponseRedirect from django.urls import reverse +from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ from django_object_actions import DjangoObjectActions, action @@ -62,7 +63,7 @@ def render(self, name, value, attrs=None, renderer=None): data_list_id = f"datalist_{name}" options = "\n".join(f'