From 2aa4742ec88be4cd07f569805d22a35c08a08f40 Mon Sep 17 00:00:00 2001 From: Avinesh Kumar Date: Wed, 6 Mar 2024 20:38:05 +0100 Subject: [PATCH 01/46] typo fix --- patchwork/templates/patchwork/bundles.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patchwork/templates/patchwork/bundles.html b/patchwork/templates/patchwork/bundles.html index cc2ebf90d..6d65a7e8d 100644 --- a/patchwork/templates/patchwork/bundles.html +++ b/patchwork/templates/patchwork/bundles.html @@ -56,7 +56,7 @@

Bundles

Bundles are groups of related patches. You can create bundles by selecting patches from a project, then using the 'create bundle' form to give your bundle a name. Each bundle can be public or private; public - bundles are given a persistent URL, based you your username and the name + bundles are given a persistent URL, based on your username and the name of the bundle. Private bundles are only visible to you.

From 0f50a62d29b7dae730513f81abac9db24e814598 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 8 Apr 2024 10:52:17 +0100 Subject: [PATCH 02/46] trivial: Prepare for ruff bump Signed-off-by: Stephen Finucane --- patchwork/models.py | 1 - patchwork/tests/test_notifications.py | 1 - patchwork/tests/views/test_api.py | 1 - patchwork/tests/views/test_bundles.py | 4 ---- patchwork/tests/views/test_mail.py | 3 --- patchwork/tests/views/test_utils.py | 4 ++-- patchwork/views/mail.py | 6 +++--- 7 files changed, 5 insertions(+), 15 deletions(-) diff --git a/patchwork/models.py b/patchwork/models.py index 9a619bc56..4e5afc4ba 100644 --- a/patchwork/models.py +++ b/patchwork/models.py @@ -1059,7 +1059,6 @@ def __str__(self): class Check(models.Model): - """Check for a patch. Checks store the results of any tests executed (or executing) for a diff --git a/patchwork/tests/test_notifications.py b/patchwork/tests/test_notifications.py index 70d25da36..b56c8ccbe 100644 --- a/patchwork/tests/test_notifications.py +++ b/patchwork/tests/test_notifications.py @@ -20,7 +20,6 @@ class PatchNotificationModelTest(TestCase): - """Tests for the creation and update of the PatchChangeNotifications.""" def setUp(self): diff --git a/patchwork/tests/views/test_api.py b/patchwork/tests/views/test_api.py index b14d402b6..4b6accdb1 100644 --- a/patchwork/tests/views/test_api.py +++ b/patchwork/tests/views/test_api.py @@ -12,7 +12,6 @@ class SubmitterCompletionTest(TestCase): - """Validate the 'submitter' autocomplete endpoint.""" def test_name_complete(self): diff --git a/patchwork/tests/views/test_bundles.py b/patchwork/tests/views/test_bundles.py index e5ac1efad..517b4439b 100644 --- a/patchwork/tests/views/test_bundles.py +++ b/patchwork/tests/views/test_bundles.py @@ -222,7 +222,6 @@ def setUp(self): class BundlePublicModifyTest(BundleTestBase): - """Ensure that non-owners can't modify bundles""" def setUp(self): @@ -275,7 +274,6 @@ def test_bundle_form_submission(self): class BundlePrivateViewTest(BundleTestBase): - """Ensure that non-owners can't view private bundles""" def setUp(self): @@ -306,7 +304,6 @@ def test_private_bundle(self): @override_settings(ENABLE_REST_API=True) class BundlePrivateViewMboxTest(BundlePrivateViewTest): - """Ensure that non-owners can't view private bundle mboxes""" def setUp(self): @@ -704,7 +701,6 @@ def test_add_to_non_empty_bundle(self): class BundleInitialOrderTest(BundleTestBase): - """When creating bundles from a patch list, ensure that the patches in the bundle are ordered by date""" diff --git a/patchwork/tests/views/test_mail.py b/patchwork/tests/views/test_mail.py index ae0b2c388..798b83f48 100644 --- a/patchwork/tests/views/test_mail.py +++ b/patchwork/tests/views/test_mail.py @@ -175,7 +175,6 @@ def test_valid_hash(self): class OptoutPreexistingTest(OptoutTest): - """Test that a duplicated opt-out behaves the same as the initial one""" def setUp(self): @@ -280,7 +279,6 @@ def test_valid_hash(self): class OptinWithoutOptoutTest(TestCase): - """Test an opt-in with no existing opt-out.""" def test_opt_in_without_optout(self): @@ -294,7 +292,6 @@ def test_opt_in_without_optout(self): class UserProfileOptoutFormTest(TestCase): - """Validate presence of correct optin/optout forms.""" form_re_template = ( diff --git a/patchwork/tests/views/test_utils.py b/patchwork/tests/views/test_utils.py index 61fa07633..91f6b86e5 100644 --- a/patchwork/tests/views/test_utils.py +++ b/patchwork/tests/views/test_utils.py @@ -38,11 +38,11 @@ def test_utf8_nbsp_tags(self): """Test that UTF-8 NBSP characters are correctly handled.""" patch = create_patch(content='patch text\n') create_patch_comment( - patch=patch, content='comment\nAcked-by:\u00A0 foo' + patch=patch, content='comment\nAcked-by:\u00a0 foo' ) mbox = utils.patch_to_mbox(patch) - self.assertIn('\u00A0 foo\n', mbox) + self.assertIn('\u00a0 foo\n', mbox) def test_multiple_tags(self): """Test that the mbox view appends tags correct. diff --git a/patchwork/views/mail.py b/patchwork/views/mail.py index 7c864c9ce..8709f0ad0 100644 --- a/patchwork/views/mail.py +++ b/patchwork/views/mail.py @@ -78,9 +78,9 @@ def _optinout(request, action): form = EmailForm(data=request.POST) if not form.is_valid(): - context[ - 'error' - ] = 'There was an error in the form. Please review and re-submit.' + context['error'] = ( + 'There was an error in the form. Please review and re-submit.' + ) context['form'] = form return render(request, html_template, context) From ffbd322dd911de65444e734c2ec69047c17bb565 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Mon, 8 Apr 2024 10:52:30 +0100 Subject: [PATCH 03/46] pre-commit: Bump versions Signed-off-by: Stephen Finucane --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 367d7e05c..5cc90fce5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ default_language_version: python: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-executables-have-shebangs - id: check-merge-conflict @@ -14,7 +14,7 @@ repos: - id: trailing-whitespace exclude: (.*\.mbox)|(.*\.svg) - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.5.4 + rev: v1.5.5 hooks: - id: remove-tabs exclude: (.*\.mbox)|(.*\.svg)|(.*\.sql)|(.*\.conf) @@ -22,7 +22,7 @@ repos: exclude: (.*\.mbox)|(.*\.svg) - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.1.13 + rev: v0.5.1 hooks: # Run the linter. - id: ruff @@ -33,7 +33,7 @@ repos: hooks: - id: bashate - repo: https://github.com/daveshanley/vacuum - rev: v0.7.2 + rev: v0.11.1 hooks: - id: vacuum files: ^docs/api/schemas/(latest|v\d\.\d)/patchwork.yaml From 3e5bb7e1b28b8221f6b77f9b1ba22b3503a1fc04 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 12 Jul 2024 14:32:43 +0100 Subject: [PATCH 04/46] docker: Make .env file optional I now have a standard UID and GID of 1000, making this file unnecessary. Signed-off-by: Stephen Finucane --- README.rst | 7 +++++-- docs/development/installation.rst | 22 ++++++++-------------- tools/docker/Dockerfile | 10 ++-------- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/README.rst b/README.rst index 979801fb3..ebde1f363 100644 --- a/README.rst +++ b/README.rst @@ -85,11 +85,14 @@ environment. To install Patchwork: $ git clone https://github.com/getpatchwork/patchwork.git -3. Create a ``.env`` file in the root directory of the project and store your - ``UID`` and ``GID`` attributes there:: +3. (Optional) Create a ``.env`` file in the root directory of the project and + store your ``UID`` and ``GID`` attributes there:: $ cd patchwork && printf "UID=$(id -u)\nGID=$(id -g)\n" > .env + This should only be necessary if you have a ``UID`` or ``GID`` other than + ``1000``. + 4. Build the images. This will download a number of packages from the internet, and compile several versions of Python:: diff --git a/docs/development/installation.rst b/docs/development/installation.rst index 6f4920a62..c33f768b1 100644 --- a/docs/development/installation.rst +++ b/docs/development/installation.rst @@ -30,14 +30,20 @@ configure Patchwork using Docker: package. __ post-install_ -#. Create a ``.env`` file in the root directory of the project and store your - ``UID`` and ``GID`` attribute there. +#. (Optional) Create a ``.env`` file in the root directory of the project and + store your ``UID`` and ``GID`` attribute there. .. code-block:: shell $ echo "UID=$UID" > .env $ echo "GID=`id -g`" >> .env + This should only be necessary if you have a ``UID`` or ``GID`` other than + ``1000``. For more information on why this is necessary, refer to this + `docker-compose issue`__. + + __ https://github.com/docker/compose/issues/2380 + #. Build the images. This will download over 200MB from the internet: .. code-block:: shell @@ -140,18 +146,6 @@ For more information on Docker itself, please refer to the `docker`_ and __ post-install_ -.. note:: - - If you see an error like the below:: - - You must define UID in .env - - Ensure you have created a ``.env`` file in the root of your project - directory and stored the ``UID`` attribute there. For more information on - why this is necessary, refer to this `docker-compose issue`__. - - __ https://github.com/docker/compose/issues/2380 - .. _docker: https://docs.docker.com/engine/install/ .. _docker-compose: https://docs.docker.com/compose/install/ .. _post-install: https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile index bdae67a27..0a55b54db 100644 --- a/tools/docker/Dockerfile +++ b/tools/docker/Dockerfile @@ -1,13 +1,7 @@ FROM ghcr.io/getpatchwork/pyenv:latest -ARG UID -ARG GID - -# make sure the user has configured the '.env' file and quick fail if not - -RUN echo $UID; echo $GID; \ - [ -n "$UID" ] || { echo "You must define UID in .env" 1>&2; exit 1; }; \ - [ -n "$GID" ] || { echo "You must define GID in .env" 1>&2; exit 1; } +ARG UID=1000 +ARG GID=1000 ARG TZ="Australia/Canberra" ENV DEBIAN_FRONTEND noninteractive From 47add6b59dcd15f1bf93e4edbac3603b199a4ba6 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 12 Jul 2024 10:38:34 +0100 Subject: [PATCH 05/46] requirements: Bump various libraries djangorestframework, django-filter, django-debug-toolbar, sqlparse, python-dateutil and openapi-core all have updates. The only change here is, yet again, openapi-core related, as we swap out a deprecated API with its replacement. Signed-off-by: Stephen Finucane --- patchwork/tests/api/validator.py | 3 ++- requirements-dev.txt | 6 +++--- requirements-prod.txt | 6 +++--- requirements-test.txt | 7 ++++--- tox.ini | 8 ++++---- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/patchwork/tests/api/validator.py b/patchwork/tests/api/validator.py index b6c64ef03..7fd279878 100644 --- a/patchwork/tests/api/validator.py +++ b/patchwork/tests/api/validator.py @@ -7,6 +7,7 @@ import re from django.urls import resolve +import jsonschema_path import openapi_core from openapi_core.contrib.django import DjangoOpenAPIRequest from openapi_core.contrib.django import DjangoOpenAPIResponse @@ -88,7 +89,7 @@ def _load_spec(version): with open(spec_path, 'r') as fh: data = yaml.load(fh, Loader=yaml.SafeLoader) - _LOADED_SPECS[version] = openapi_core.Spec.from_dict(data) + _LOADED_SPECS[version] = jsonschema_path.SchemaPath.from_dict(data) return _LOADED_SPECS[version] diff --git a/requirements-dev.txt b/requirements-dev.txt index 47ee9afe8..586f93e16 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ Django~=5.0.0 -djangorestframework~=3.14.0 -django-filter~=23.5.0 -django-debug-toolbar~=4.2.0 +djangorestframework~=3.15.2 +django-filter~=24.2.0 +django-debug-toolbar~=4.4.0 django-dbbackup~=4.1.0 -r requirements-test.txt diff --git a/requirements-prod.txt b/requirements-prod.txt index 75c3825e3..9c9b3ffab 100644 --- a/requirements-prod.txt +++ b/requirements-prod.txt @@ -1,5 +1,5 @@ Django~=5.0.0 -djangorestframework~=3.14.0 -django-filter~=23.5.0 +djangorestframework~=3.15.0 +django-filter~=24.2.0 psycopg2~=2.9.0 -sqlparse~=0.4.0 +sqlparse~=0.5.0 diff --git a/requirements-test.txt b/requirements-test.txt index 5e9000edf..d6b6225aa 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,7 +1,8 @@ mysqlclient~=2.2.0 psycopg2-binary~=2.9.0 -sqlparse~=0.4.0 -python-dateutil~=2.8.0 +sqlparse~=0.5.0 +python-dateutil~=2.9.0 tblib~=3.0.0 -openapi-core~=0.18.0 +openapi-core~=0.19.0 +jsonschema-path~=0.3.3 termcolor~=2.4.0 diff --git a/tox.ini b/tox.ini index 041097cd5..3df1a7ed7 100644 --- a/tox.ini +++ b/tox.ini @@ -7,11 +7,11 @@ skip_install = true deps = -r{toxinidir}/requirements-test.txt django42: django~=4.2.0 - django42: djangorestframework~=3.14.0 - django42: django-filter~=23.5.0 + django42: djangorestframework~=3.15.0 + django42: django-filter~=24.2.0 django50: django~=5.0.0 - django50: djangorestframework~=3.14.0 - django50: django-filter~=23.5.0 + django50: djangorestframework~=3.15.0 + django50: django-filter~=24.2.0 setenv = DJANGO_SETTINGS_MODULE = patchwork.settings.dev PYTHONDONTWRITEBYTECODE = 1 From b648ba0c814bbc60c734b10687032c4f7a91ec2a Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 12 Jul 2024 16:20:09 +0100 Subject: [PATCH 06/46] Remove unused import Signed-off-by: Stephen Finucane --- patchwork/tests/api/validator.py | 1 - 1 file changed, 1 deletion(-) diff --git a/patchwork/tests/api/validator.py b/patchwork/tests/api/validator.py index 7fd279878..f7617e453 100644 --- a/patchwork/tests/api/validator.py +++ b/patchwork/tests/api/validator.py @@ -8,7 +8,6 @@ from django.urls import resolve import jsonschema_path -import openapi_core from openapi_core.contrib.django import DjangoOpenAPIRequest from openapi_core.contrib.django import DjangoOpenAPIResponse from openapi_core.exceptions import OpenAPIError From 33b209d76b6a7dbb02dd7467cff9c9e24d3637fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 15:24:53 +0000 Subject: [PATCH 07/46] build(deps): update psycopg2 requirement from ~=2.9.0 to ~=2.9.9 Updates the requirements on [psycopg2](https://github.com/psycopg/psycopg2) to permit the latest version. - [Changelog](https://github.com/psycopg/psycopg2/blob/master/NEWS) - [Commits](https://github.com/psycopg/psycopg2/compare/2.9.6...2.9.9) --- updated-dependencies: - dependency-name: psycopg2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements-prod.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-prod.txt b/requirements-prod.txt index 9c9b3ffab..8fa04407e 100644 --- a/requirements-prod.txt +++ b/requirements-prod.txt @@ -1,5 +1,5 @@ Django~=5.0.0 djangorestframework~=3.15.0 django-filter~=24.2.0 -psycopg2~=2.9.0 +psycopg2~=2.9.9 sqlparse~=0.5.0 From 1921a9b8ee3c22c6363df6253ca5752b7071ae3c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 16:23:43 +0000 Subject: [PATCH 08/46] build(deps): update django requirement from ~=5.0.0 to ~=5.0.7 Updates the requirements on [django](https://github.com/django/django) to permit the latest version. - [Commits](https://github.com/django/django/compare/5.0...5.0.7) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- requirements-prod.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 586f93e16..3d80e0cf9 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,4 @@ -Django~=5.0.0 +Django~=5.0.7 djangorestframework~=3.15.2 django-filter~=24.2.0 django-debug-toolbar~=4.4.0 diff --git a/requirements-prod.txt b/requirements-prod.txt index 8fa04407e..c596bc4ac 100644 --- a/requirements-prod.txt +++ b/requirements-prod.txt @@ -1,4 +1,4 @@ -Django~=5.0.0 +Django~=5.0.7 djangorestframework~=3.15.0 django-filter~=24.2.0 psycopg2~=2.9.9 From e879cdaf7dceff98fdc1777f8d6286aa2e01d14a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 16:23:39 +0000 Subject: [PATCH 09/46] build(deps): update psycopg2-binary requirement from ~=2.9.0 to ~=2.9.9 Updates the requirements on [psycopg2-binary](https://github.com/psycopg/psycopg2) to permit the latest version. - [Changelog](https://github.com/psycopg/psycopg2/blob/master/NEWS) - [Commits](https://github.com/psycopg/psycopg2/compare/2.9.6...2.9.9) --- updated-dependencies: - dependency-name: psycopg2-binary dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-test.txt b/requirements-test.txt index d6b6225aa..9d316c67b 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,5 +1,5 @@ mysqlclient~=2.2.0 -psycopg2-binary~=2.9.0 +psycopg2-binary~=2.9.9 sqlparse~=0.5.0 python-dateutil~=2.9.0 tblib~=3.0.0 From 4ec9033af201a93447453b30f1009822469f4cb9 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 12 Jul 2024 17:21:52 +0100 Subject: [PATCH 10/46] pre-commit: Everything in Python 3 now No need to set the default Python version. Signed-off-by: Stephen Finucane --- .pre-commit-config.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5cc90fce5..21ab40e7e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,4 @@ --- -default_language_version: - # force all unspecified python hooks to run python3 - python: python3 repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 From c6fa6e7919345c065ced08cfb5499185c651145d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 11:56:13 +0000 Subject: [PATCH 11/46] build(deps): update openapi-core requirement from ~=0.19.0 to ~=0.19.2 Updates the requirements on [openapi-core](https://github.com/python-openapi/openapi-core) to permit the latest version. - [Release notes](https://github.com/python-openapi/openapi-core/releases) - [Commits](https://github.com/python-openapi/openapi-core/compare/0.19.0...0.19.2) --- updated-dependencies: - dependency-name: openapi-core dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-test.txt b/requirements-test.txt index 9d316c67b..bed113298 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -3,6 +3,6 @@ psycopg2-binary~=2.9.9 sqlparse~=0.5.0 python-dateutil~=2.9.0 tblib~=3.0.0 -openapi-core~=0.19.0 +openapi-core~=0.19.2 jsonschema-path~=0.3.3 termcolor~=2.4.0 From 955e80cbe183cace1c1e97ba3a0ff83560194d0e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 11:56:09 +0000 Subject: [PATCH 12/46] build(deps): update mysqlclient requirement from ~=2.2.0 to ~=2.2.4 Updates the requirements on [mysqlclient](https://github.com/PyMySQL/mysqlclient) to permit the latest version. - [Release notes](https://github.com/PyMySQL/mysqlclient/releases) - [Changelog](https://github.com/PyMySQL/mysqlclient/blob/main/HISTORY.rst) - [Commits](https://github.com/PyMySQL/mysqlclient/compare/v2.2.0...v2.2.4) --- updated-dependencies: - dependency-name: mysqlclient dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-test.txt b/requirements-test.txt index bed113298..242c02226 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,4 +1,4 @@ -mysqlclient~=2.2.0 +mysqlclient~=2.2.4 psycopg2-binary~=2.9.9 sqlparse~=0.5.0 python-dateutil~=2.9.0 From 77b20604a1d5727cc9fb78247097bedb0259a849 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 11:56:06 +0000 Subject: [PATCH 13/46] build(deps-dev): update django-debug-toolbar requirement Updates the requirements on [django-debug-toolbar](https://github.com/jazzband/django-debug-toolbar) to permit the latest version. - [Release notes](https://github.com/jazzband/django-debug-toolbar/releases) - [Changelog](https://github.com/jazzband/django-debug-toolbar/blob/main/docs/changes.rst) - [Commits](https://github.com/jazzband/django-debug-toolbar/compare/4.4...4.4.6) --- updated-dependencies: - dependency-name: django-debug-toolbar dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 3d80e0cf9..e84b73918 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ Django~=5.0.7 djangorestframework~=3.15.2 django-filter~=24.2.0 -django-debug-toolbar~=4.4.0 +django-debug-toolbar~=4.4.6 django-dbbackup~=4.1.0 -r requirements-test.txt From 4dfe6991a7bcdb11fd878a087aba314e9fdaa2db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:50:44 +0000 Subject: [PATCH 14/46] build(deps): update sqlparse requirement from ~=0.5.0 to ~=0.5.1 Updates the requirements on [sqlparse](https://github.com/andialbrecht/sqlparse) to permit the latest version. - [Changelog](https://github.com/andialbrecht/sqlparse/blob/master/CHANGELOG) - [Commits](https://github.com/andialbrecht/sqlparse/compare/0.5.0...0.5.1) --- updated-dependencies: - dependency-name: sqlparse dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements-prod.txt | 2 +- requirements-test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-prod.txt b/requirements-prod.txt index c596bc4ac..6e91bfb82 100644 --- a/requirements-prod.txt +++ b/requirements-prod.txt @@ -2,4 +2,4 @@ Django~=5.0.7 djangorestframework~=3.15.0 django-filter~=24.2.0 psycopg2~=2.9.9 -sqlparse~=0.5.0 +sqlparse~=0.5.1 diff --git a/requirements-test.txt b/requirements-test.txt index 242c02226..74dc7ddeb 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,6 +1,6 @@ mysqlclient~=2.2.4 psycopg2-binary~=2.9.9 -sqlparse~=0.5.0 +sqlparse~=0.5.1 python-dateutil~=2.9.0 tblib~=3.0.0 openapi-core~=0.19.2 From af477a9bb40519edba3ad7a100ada777ae994bb9 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 23 Oct 2024 17:53:48 +0100 Subject: [PATCH 15/46] CI: docker-compose -> docker compose Per [1]. [1] https://github.com/orgs/community/discussions/116610 Signed-off-by: Stephen Finucane --- .github/workflows/ci.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a4a65789b..70e1b03c8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -135,30 +135,30 @@ jobs: python-version: "3.12" - name: Build docker-compose service run: | - docker-compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g) + docker compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g) - name: Test createsuperuser/changepassword run: | - docker-compose run -T --rm web \ + docker compose run -T --rm web \ python manage.py createsuperuser \ --username patchwork --no-input --email test@example.com { echo patchwork; echo patchwork; } | \ - docker-compose run -T --rm web \ + docker compose run -T --rm web \ python manage.py changepassword patchwork # FIXME(stephenfin): Re-enable this once dbbackup supports Django 4.0 # - name: Test dbbackup/dbrestore # run: | - # docker-compose run -T --rm web python manage.py dbbackup - # echo y | docker-compose run -T --rm web python manage.py dbrestore + # docker compose run -T --rm web python manage.py dbbackup + # echo y | docker compose run -T --rm web python manage.py dbrestore - name: Modify database user permissions (mysql) if: ${{ matrix.db == 'mysql' }} run: | - docker-compose exec -T -- db \ + docker compose exec -T -- db \ sh -c "exec mysql -uroot -p\"\${MYSQL_ROOT_PASSWORD}\" -e \"GRANT ALL ON \\\`test\\_\${MYSQL_DATABASE}%\\\`.* to '\${MYSQL_USER}'@'%'; FLUSH PRIVILEGES;\"" - name: Run unittest - run: docker-compose run -T --rm web tox + run: docker compose run -T --rm web tox - name: Test normal startup run: | - docker-compose up --detach + docker compose up --detach for count in $(seq 50); do \ if curl --fail --silent "http://localhost:8000"; then \ @@ -168,7 +168,7 @@ jobs: done echo - docker-compose ps + docker compose ps - name: Test client access (git-pw) run: | python -m pip install git-pw @@ -177,4 +177,4 @@ jobs: --username patchwork --password patchwork series list - name: Dump container logs if: ${{ always() }} - run: docker-compose logs --no-color --timestamps + run: docker compose logs --no-color --timestamps From 009dc90b90b9a8493962a804475a3d2afa9d528f Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 23 Oct 2024 17:38:53 +0100 Subject: [PATCH 16/46] pre-commit: Bump versions Signed-off-by: Stephen Finucane --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 21ab40e7e..d67cf098e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ --- repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-executables-have-shebangs - id: check-merge-conflict @@ -19,7 +19,7 @@ repos: exclude: (.*\.mbox)|(.*\.svg) - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.5.1 + rev: v0.7.0 hooks: # Run the linter. - id: ruff @@ -30,7 +30,7 @@ repos: hooks: - id: bashate - repo: https://github.com/daveshanley/vacuum - rev: v0.11.1 + rev: v0.12.1 hooks: - id: vacuum files: ^docs/api/schemas/(latest|v\d\.\d)/patchwork.yaml From 18e91c47c133899e2092c6ed41dcda378c30fb71 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 23 Oct 2024 17:37:18 +0100 Subject: [PATCH 17/46] Drop Python 3.8 support We also add a release note for Python 3.12 support. Signed-off-by: Stephen Finucane --- .github/workflows/ci.yaml | 2 +- .../notes/python-3-12-support-db9b9726661b70e9.yaml | 9 +++++++++ tox.ini | 3 +-- 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/python-3-12-support-db9b9726661b70e9.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 70e1b03c8..52b186027 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -29,7 +29,7 @@ jobs: matrix: # NOTE: If you add a version here, don't forget to update the # '[gh-actions]' section in tox.ini - python: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python: ["3.9", "3.10", "3.11", "3.12"] db: [postgres, mysql, sqlite3] env: DATABASE_TYPE: "${{ matrix.db }}" diff --git a/releasenotes/notes/python-3-12-support-db9b9726661b70e9.yaml b/releasenotes/notes/python-3-12-support-db9b9726661b70e9.yaml new file mode 100644 index 000000000..2ceca1b77 --- /dev/null +++ b/releasenotes/notes/python-3-12-support-db9b9726661b70e9.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + `Python 3.12 `_ is + now supported. +upgrade: + - | + Python 3.8 is no longer supported. It is no longer supported upstream and + most distributions provide a newer version. diff --git a/tox.ini b/tox.ini index 3df1a7ed7..44dafb44b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 3.2 -envlist = pep8,docs,py{38,39,310,311}-django42,py{310,311,312}-django50 +envlist = pep8,docs,py{39,310,311}-django42,py{310,311,312}-django50 [testenv] skip_install = true @@ -71,7 +71,6 @@ commands = [gh-actions] python = - 3.8: py38 3.9: py39 3.10: py310 3.11: py311 From 5e2f3b8c0cd50812aaf52755782bffa24e821185 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 23 Oct 2024 17:47:36 +0100 Subject: [PATCH 18/46] requirements: Bump Django to 5.1.x, django-filter to 24.3.0 Signed-off-by: Stephen Finucane --- tox.ini | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 44dafb44b..b101b796b 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 3.2 -envlist = pep8,docs,py{39,310,311}-django42,py{310,311,312}-django50 +envlist = pep8,docs,py{39,310,311}-django42,py{310,311,312}-django{50,51} [testenv] skip_install = true @@ -8,10 +8,13 @@ deps = -r{toxinidir}/requirements-test.txt django42: django~=4.2.0 django42: djangorestframework~=3.15.0 - django42: django-filter~=24.2.0 + django42: django-filter~=24.3.0 django50: django~=5.0.0 django50: djangorestframework~=3.15.0 - django50: django-filter~=24.2.0 + django50: django-filter~=24.3.0 + django51: django~=5.1.0 + django51: djangorestframework~=3.15.0 + django51: django-filter~=24.3.0 setenv = DJANGO_SETTINGS_MODULE = patchwork.settings.dev PYTHONDONTWRITEBYTECODE = 1 From 80d5962a68af429d6624a3a71186e5207bc38b92 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 23 Oct 2024 17:47:54 +0100 Subject: [PATCH 19/46] Add Python 3.13 support Signed-off-by: Stephen Finucane --- .github/workflows/ci.yaml | 8 ++++---- .../notes/python-3-13-support-dc538fa3bc716863.yaml | 5 +++++ tox.ini | 3 ++- 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/python-3-13-support-dc538fa3bc716863.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 52b186027..5fb784df3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -13,7 +13,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.12" + python-version: "3.13" - name: Set up Go uses: actions/setup-go@v4 with: @@ -29,7 +29,7 @@ jobs: matrix: # NOTE: If you add a version here, don't forget to update the # '[gh-actions]' section in tox.ini - python: ["3.9", "3.10", "3.11", "3.12"] + python: ["3.9", "3.10", "3.11", "3.12", "3.13"] db: [postgres, mysql, sqlite3] env: DATABASE_TYPE: "${{ matrix.db }}" @@ -105,7 +105,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.12" + python-version: "3.13" - name: Install dependencies run: python -m pip install tox - name: Build docs (via tox) @@ -132,7 +132,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.12" + python-version: "3.13" - name: Build docker-compose service run: | docker compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g) diff --git a/releasenotes/notes/python-3-13-support-dc538fa3bc716863.yaml b/releasenotes/notes/python-3-13-support-dc538fa3bc716863.yaml new file mode 100644 index 000000000..f6831d827 --- /dev/null +++ b/releasenotes/notes/python-3-13-support-dc538fa3bc716863.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + `Python 3.13 `_ is + now supported. diff --git a/tox.ini b/tox.ini index b101b796b..f1385fda7 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 3.2 -envlist = pep8,docs,py{39,310,311}-django42,py{310,311,312}-django{50,51} +envlist = pep8,docs,py{39,310,311}-django42,py{310,311,312}-django{50,51},py313-django51 [testenv] skip_install = true @@ -78,3 +78,4 @@ python = 3.10: py310 3.11: py311 3.12: py312 + 3.13: py313 From 8242e9dfff1c784f232614e02c416b16f56f5d88 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 23 Oct 2024 18:10:51 +0100 Subject: [PATCH 20/46] requirements: Bump versions We also add a release note for the Django version bump. We should have done both in commit 5e2f3b8c0c. Signed-off-by: Stephen Finucane --- releasenotes/notes/django-5-1-support-83a264d5f28e32c1.yaml | 5 +++++ requirements-dev.txt | 4 ++-- requirements-prod.txt | 6 +++--- requirements-test.txt | 4 ++-- 4 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/django-5-1-support-83a264d5f28e32c1.yaml diff --git a/releasenotes/notes/django-5-1-support-83a264d5f28e32c1.yaml b/releasenotes/notes/django-5-1-support-83a264d5f28e32c1.yaml new file mode 100644 index 000000000..c433eb41e --- /dev/null +++ b/releasenotes/notes/django-5-1-support-83a264d5f28e32c1.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + `Django 5.1 `_ is + now supported. diff --git a/requirements-dev.txt b/requirements-dev.txt index e84b73918..123bd88fd 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,6 +1,6 @@ -Django~=5.0.7 +Django~=5.1.0 djangorestframework~=3.15.2 django-filter~=24.2.0 django-debug-toolbar~=4.4.6 -django-dbbackup~=4.1.0 +django-dbbackup~=4.2.0 -r requirements-test.txt diff --git a/requirements-prod.txt b/requirements-prod.txt index 6e91bfb82..4bd824a7b 100644 --- a/requirements-prod.txt +++ b/requirements-prod.txt @@ -1,5 +1,5 @@ -Django~=5.0.7 +Django~=5.1.0 djangorestframework~=3.15.0 -django-filter~=24.2.0 -psycopg2~=2.9.9 +django-filter~=24.3.0 +psycopg2~=2.9.10 sqlparse~=0.5.1 diff --git a/requirements-test.txt b/requirements-test.txt index 74dc7ddeb..1371abd39 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,8 +1,8 @@ -mysqlclient~=2.2.4 +mysqlclient~=2.2.5 psycopg2-binary~=2.9.9 sqlparse~=0.5.1 python-dateutil~=2.9.0 tblib~=3.0.0 openapi-core~=0.19.2 jsonschema-path~=0.3.3 -termcolor~=2.4.0 +termcolor~=2.5.0 From 9d2eaff55c2e2e19ac3bca1618e7e408927d1789 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 23 Oct 2024 18:14:49 +0100 Subject: [PATCH 21/46] docker-compose: Remove version This is obsolete, apparently. Signed-off-by: Stephen Finucane --- docker-compose-pg.yml | 2 +- docker-compose-sqlite3.yml | 2 +- docker-compose.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose-pg.yml b/docker-compose-pg.yml index 645f93e16..44bc3ec0a 100644 --- a/docker-compose-pg.yml +++ b/docker-compose-pg.yml @@ -1,4 +1,4 @@ -version: "3" +--- services: db: image: postgres:latest diff --git a/docker-compose-sqlite3.yml b/docker-compose-sqlite3.yml index d4c66593f..900cb71fd 100644 --- a/docker-compose-sqlite3.yml +++ b/docker-compose-sqlite3.yml @@ -1,4 +1,4 @@ -version: "3" +--- services: web: build: diff --git a/docker-compose.yml b/docker-compose.yml index 9bff93f65..73f080a49 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: "3" +--- services: db: image: mysql:latest From bbcefad667fb8c5026cdc55996c08d23635f668f Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 23 Oct 2024 18:21:04 +0100 Subject: [PATCH 22/46] dependabot: Ignore (most) patch update Signed-off-by: Stephen Finucane --- .github/dependabot.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e3e110127..df0e4c6c1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,6 +1,11 @@ +--- version: 2 updates: - package-ecosystem: "pip" directory: "/" # Location of package manifests schedule: interval: "weekly" + versioning-strategy: "increase" + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-patch"] From b8856bbb9567f81f71f692d76cefd3f8559213d8 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 23 Oct 2024 18:22:31 +0100 Subject: [PATCH 23/46] dependabot: Manage GitHub Action workflows also Signed-off-by: Stephen Finucane --- .github/dependabot.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index df0e4c6c1..d1d60e3fa 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,3 +9,7 @@ updates: ignore: - dependency-name: "*" update-types: ["version-update:semver-patch"] + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" From fe7a277902687e525fe84fd8e1a602b6089ee2d0 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 23 Oct 2024 18:28:23 +0100 Subject: [PATCH 24/46] docs: Update release note to reflect timelines Signed-off-by: Stephen Finucane --- releasenotes/notes/django-4-1-support-bcbe65a71d235b43.yaml | 5 ----- releasenotes/notes/django-5-0-support-923e45ec2dc93117.yaml | 5 ++--- 2 files changed, 2 insertions(+), 8 deletions(-) delete mode 100644 releasenotes/notes/django-4-1-support-bcbe65a71d235b43.yaml diff --git a/releasenotes/notes/django-4-1-support-bcbe65a71d235b43.yaml b/releasenotes/notes/django-4-1-support-bcbe65a71d235b43.yaml deleted file mode 100644 index 3dcab1bdf..000000000 --- a/releasenotes/notes/django-4-1-support-bcbe65a71d235b43.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -features: - - | - `Django 4.1 `_ is - now supported. diff --git a/releasenotes/notes/django-5-0-support-923e45ec2dc93117.yaml b/releasenotes/notes/django-5-0-support-923e45ec2dc93117.yaml index 84ec05969..2e7aa35e5 100644 --- a/releasenotes/notes/django-5-0-support-923e45ec2dc93117.yaml +++ b/releasenotes/notes/django-5-0-support-923e45ec2dc93117.yaml @@ -5,6 +5,5 @@ features: now supported. upgrade: - | - Django 3.2 and 4.1 are no longer supported. 4.1 is no longer supported - upstream while Django 3.2 goes EOL in April 2024. Most distributions - provide a newer version. + Django 3.2 and 4.1 are no longer supported. Bother releases are no longer + supported upstream and most distributions provide a newer version. From 1d5c43618bb3c9df1265d19d2f4cf4775b28c6e3 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 23 Oct 2024 18:35:50 +0100 Subject: [PATCH 25/46] Release 3.2.0 Another smaller release, this time with no new API versions. Signed-off-by: Stephen Finucane --- releasenotes/notes/prelude-3_2-fadbacb4fe4227b4.yaml | 8 ++++++++ version.txt | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/prelude-3_2-fadbacb4fe4227b4.yaml diff --git a/releasenotes/notes/prelude-3_2-fadbacb4fe4227b4.yaml b/releasenotes/notes/prelude-3_2-fadbacb4fe4227b4.yaml new file mode 100644 index 000000000..743274fd5 --- /dev/null +++ b/releasenotes/notes/prelude-3_2-fadbacb4fe4227b4.yaml @@ -0,0 +1,8 @@ +--- +prelude: > + This release is another smaller release that focuses on bumping the + supported dependencies and updates the test matrix in advance of future, + larger changes. As always, recent versions of Python (3.12, 3.13) and + Django (5.0, 5.1) are now supported, while support for older Python + (3.7, 3.8) and Django (3.2, 4.0, 4.1) versions has been removed. More + information is included below. diff --git a/version.txt b/version.txt index 28f243b61..944880fa1 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.2.0.a.0 +3.2.0 From 8d204c07571606545882f3cb2aa6dac6d8a70bd8 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 23 Oct 2024 18:37:48 +0100 Subject: [PATCH 26/46] Post-release version bump Signed-off-by: Stephen Finucane --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 944880fa1..d4f3a239f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.2.0 +3.3.0.a.0 From e2c37f171b942a824d82f8dd7858529113dccfb8 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 23 Oct 2024 18:40:35 +0100 Subject: [PATCH 27/46] Update reno for stable/3.2 Signed-off-by: Stephen Finucane --- docs/index.rst | 1 + docs/releases/index.rst | 1 + docs/releases/iridescent.rst | 5 +++++ docs/releases/unreleased.rst | 2 +- 4 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 docs/releases/iridescent.rst diff --git a/docs/index.rst b/docs/index.rst index b58cdc300..33b4981fa 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -58,6 +58,7 @@ of community projects. :caption: Release Notes releases/unreleased + releases/iridescent releases/hessian releases/grosgrain releases/flannel diff --git a/docs/releases/index.rst b/docs/releases/index.rst index 327513266..776aa60cd 100644 --- a/docs/releases/index.rst +++ b/docs/releases/index.rst @@ -10,6 +10,7 @@ on the release process, refer to :doc:`/development/releasing`. :maxdepth: 2 /releases/unreleased + releases/iridescent /releases/hessian /releases/grosgrain /releases/flannel diff --git a/docs/releases/iridescent.rst b/docs/releases/iridescent.rst new file mode 100644 index 000000000..2954c4610 --- /dev/null +++ b/docs/releases/iridescent.rst @@ -0,0 +1,5 @@ +v3.2 Series ("Iridescent") +========================== + +.. release-notes:: + :branch: stable/3.2 diff --git a/docs/releases/unreleased.rst b/docs/releases/unreleased.rst index aae1aa96c..80ff4514c 100644 --- a/docs/releases/unreleased.rst +++ b/docs/releases/unreleased.rst @@ -2,4 +2,4 @@ Unreleased ========== .. release-notes:: - :earliest-version: v3.1.0 + :earliest-version: v3.2.0 From 51581fa741b50b6684a27abb8f3d36456438cfec Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 23 Oct 2024 18:46:23 +0100 Subject: [PATCH 28/46] docs: Fix typo Signed-off-by: Stephen Finucane --- docs/releases/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/index.rst b/docs/releases/index.rst index 776aa60cd..8c862a383 100644 --- a/docs/releases/index.rst +++ b/docs/releases/index.rst @@ -10,7 +10,7 @@ on the release process, refer to :doc:`/development/releasing`. :maxdepth: 2 /releases/unreleased - releases/iridescent + /releases/iridescent /releases/hessian /releases/grosgrain /releases/flannel From 76434533224b046a3e269b184406f120557bbc81 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 23 Oct 2024 19:14:57 +0100 Subject: [PATCH 29/46] CI: Bump action versions Signed-off-by: Stephen Finucane --- .github/workflows/ci.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5fb784df3..10a302e56 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,13 +9,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.13" - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: "1.21" - name: Install dependencies @@ -68,9 +68,9 @@ jobs: --health-retries 5 steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: Install Python dependencies @@ -99,11 +99,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.13" - name: Install dependencies @@ -111,7 +111,7 @@ jobs: - name: Build docs (via tox) run: tox -e docs - name: Archive build results - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: html-docs-build path: docs/_build/html @@ -126,11 +126,11 @@ jobs: COMPOSE_FILE: ${{ matrix.db == 'mysql' && 'docker-compose.yml' || (matrix.db == 'postgres' && 'docker-compose-pg.yml') || 'docker-compose-sqlite3.yml' }} steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.13" - name: Build docker-compose service From 13bbc0d94ea035c149f8acd042d2998778b9efd9 Mon Sep 17 00:00:00 2001 From: Thomas Monjalon Date: Fri, 16 Aug 2024 09:20:28 +0200 Subject: [PATCH 30/46] templates/submission: Fix alignment of commit message Preformatted content must not be indented because any space is kept in the output, making the content wrongly indented. When aligning message headers to the left, the new HTML code has been indented including some preformatted content indented with two spaces. The fix is to remove the indent of the content. Signed-off-by: Thomas Monjalon Fixes: fe34ab2ffad3 ("patch-detail: left align message headers") Reviewed-by: Robin Jarry Reviewed-by: Stephen Finucane --- patchwork/templates/patchwork/submission.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patchwork/templates/patchwork/submission.html b/patchwork/templates/patchwork/submission.html index 85e7be4b4..e924934f6 100644 --- a/patchwork/templates/patchwork/submission.html +++ b/patchwork/templates/patchwork/submission.html @@ -262,7 +262,7 @@

Message

{{ submission.date }} UTC
-  {{ submission|commentsyntax }}
+{{ submission|commentsyntax }}
   
From 09939ba107bde85e0b9df35c0dbbf90428789a94 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 31 Oct 2024 22:18:20 +0000 Subject: [PATCH 31/46] CI: Cancel builds upon a new push Signed-off-by: Stephen Finucane --- .github/workflows/ci.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 10a302e56..dbcdd3bee 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -3,6 +3,9 @@ name: CI on: - push - pull_request +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: lint: name: Run linters From fa14cf8480d9e515ed7a769adbd9eab33c6d82d9 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 31 Oct 2024 22:59:18 +0000 Subject: [PATCH 32/46] tests: Dump more info if version test fails Signed-off-by: Stephen Finucane --- patchwork/tests/test_version.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/patchwork/tests/test_version.py b/patchwork/tests/test_version.py index 8e512084c..9cbb13051 100644 --- a/patchwork/tests/test_version.py +++ b/patchwork/tests/test_version.py @@ -32,4 +32,9 @@ def test_validate_version(self): # if the tag is missing from one, it should be missing from the other # (and vice versa) - self.assertEqual(bool(str_match.group(1)), bool(git_match.group(1))) + self.assertEqual( + bool(str_match.group(1)), + bool(git_match.group(1)), + f'mismatch between git and version.txt post-release metadata: ' + f'git={git_match.group(1)!r}, version.txt={str_match.group(1)!r}', + ) From c443cd9c0e96ca21842a188f9101b1a8244535ee Mon Sep 17 00:00:00 2001 From: Robin Jarry Date: Wed, 5 Oct 2022 16:24:05 +0200 Subject: [PATCH 33/46] css: make diff colors more accessible The colors used to display patch diffs are confusing. The context color is very similar to the added line color and the contrast between added and removed lines is very low. Originally, the choice of purple/blue (instead of the more common red/green palette) may have been made with colorblindness accessibility in mind. However, after inspecting the current colors with colorblindness "simulators", I found that the low contrast was consistent no matter what vision deficiency (if any) you might have. Update the colors to use a more common red/green palette. Add background colors to increase contrast for colorblind people. Use less confusing colors for context and diff hunks. Use normal line height to prevent background colors from overlapping. Use a different color for email quotes (blue) to avoid confusion with added lines. I have made a compilation of the current and updated color palette previews for normal vision and all common color deficiencies. I also included the same diff as seen from Github interface for reference. Link: http://files.diabeteman.com/patchwork-diff-colors/ Signed-off-by: Robin Jarry --- htdocs/css/style.css | 16 ++++++++-------- ...-colors-more-accessible-82eda58a89984d46.yaml | 5 +++++ 2 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/make-diff-colors-more-accessible-82eda58a89984d46.yaml diff --git a/htdocs/css/style.css b/htdocs/css/style.css index 9156aa6ee..1a7395109 100644 --- a/htdocs/css/style.css +++ b/htdocs/css/style.css @@ -17,7 +17,7 @@ h2 a, h2 span { } pre { - line-height: 110%; + line-height: normal; background-color: white; border-radius: 0; } @@ -354,15 +354,15 @@ button[class^=comment-action] { } .quote { - color: #007f00; + color: #365cb5; } -span.p_header { color: #2e8b57; font-weight: bold; } -span.p_chunk { color: #a52a2a; font-weight: bold; } -span.p_context { color: #a020f0; } -span.p_add { color: #008b8b; } -span.p_del { color: #6a5acd; } -span.p_mod { color: #0000ff; } +span.p_header { font-weight: bold; } +span.p_chunk { color: #329fb0; font-weight: bold; } +span.p_context { } +span.p_add { color: #1b9d09; background-color: #edffed; } +span.p_del { color: #c80101; background-color: #ffe2e2; } +span.p_mod { color: #a020f0; } .acked-by { color: #2d4566; diff --git a/releasenotes/notes/make-diff-colors-more-accessible-82eda58a89984d46.yaml b/releasenotes/notes/make-diff-colors-more-accessible-82eda58a89984d46.yaml new file mode 100644 index 000000000..f65995e51 --- /dev/null +++ b/releasenotes/notes/make-diff-colors-more-accessible-82eda58a89984d46.yaml @@ -0,0 +1,5 @@ +--- +other: + - | + The patch diff color palette was modified to make it more accessible for + all users, including those with common color deficiencies. From 801e5bd8bbb78e8459e53dcd5a0dc8eb17e92a48 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 24 Jan 2024 11:09:46 +0000 Subject: [PATCH 34/46] models: Add covering index for Patch.hash Signed-off-by: Stephen Finucane Closes: #579 Cc: Mauro Carvalho Chehab --- .../migrations/0047_add_database_indexes.py | 18 ++++++++++++++++++ patchwork/models.py | 2 +- 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 patchwork/migrations/0047_add_database_indexes.py diff --git a/patchwork/migrations/0047_add_database_indexes.py b/patchwork/migrations/0047_add_database_indexes.py new file mode 100644 index 000000000..42c119979 --- /dev/null +++ b/patchwork/migrations/0047_add_database_indexes.py @@ -0,0 +1,18 @@ +import patchwork.fields +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ('patchwork', '0046_patch_comment_events'), + ] + + operations = [ + migrations.AlterField( + model_name='patch', + name='hash', + field=patchwork.fields.HashField( + blank=True, db_index=True, max_length=40, null=True + ), + ), + ] diff --git a/patchwork/models.py b/patchwork/models.py index 4e5afc4ba..a05db7f9c 100644 --- a/patchwork/models.py +++ b/patchwork/models.py @@ -501,7 +501,7 @@ class Patch(SubmissionMixin): ) state = models.ForeignKey(State, null=True, on_delete=models.CASCADE) archived = models.BooleanField(default=False) - hash = HashField(null=True, blank=True) + hash = HashField(null=True, blank=True, db_index=True) # series metadata From 45f47e96c6c90aefac37b02e0e15052f6ba41a90 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 1 Nov 2024 15:45:03 +0000 Subject: [PATCH 35/46] views: Switch logout to POST This was deprecated in 4.1 and removed in 5.0. I missed it. [1] https://docs.djangoproject.com/en/5.0/releases/4.1/#features-deprecated-in-4-1 Signed-off-by: Stephen Finucane --- htdocs/css/style.css | 28 ++++++++++++++++++++++++++++ templates/base.html | 7 ++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/htdocs/css/style.css b/htdocs/css/style.css index 1a7395109..c4025c050 100644 --- a/htdocs/css/style.css +++ b/htdocs/css/style.css @@ -56,6 +56,34 @@ pre { color: #999; } +ul.dropdown-menu > li > form { + display: block; + width: 100%; +} + +ul.dropdown-menu > li > form > button { + /* taken from bootstrap's styling for '.dropdown-menu > li > a' */ + background: none; + border: none; + cursor: pointer; + display: block; + padding: 3px 20px; + clear: both; + font-weight: 400; + line-height: 1.42857143; + color: #333; + white-space: nowrap; + width: 100%; + text-align: left; +} + +ul.dropdown-menu > li > form > button:hover { + /* taken from bootstrap's styling for '.dropdown-menu > li > a:hover' */ + color: #262626; + text-decoration: none; + background-color: #f5f5f5; +} + form { padding: 0em; margin: 0em; diff --git a/templates/base.html b/templates/base.html index 747da5922..9519ecc55 100644 --- a/templates/base.html +++ b/templates/base.html @@ -93,7 +93,12 @@
  • Administration
  • {% endif %}
  • View profile
  • -
  • Logout
  • +
  • +
    + {% csrf_token %} + +
    +
  • {% else %} From 62032649f05610f7570b68f6ac6c30baa59fab8b Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Fri, 1 Nov 2024 13:48:28 +0000 Subject: [PATCH 36/46] views: Don't show chevron if user can't change sort order We prevent users changing the column that sorting is done on when allowing users to change the order of patches in a bundle. However, we still show a chevron and clicking the link will appear to do something. This is confusing/misleading. Remove the chevron and the link in this situation. Signed-off-by: Stephen Finucane --- .../patchwork/partials/patch-list.html | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/patchwork/templates/patchwork/partials/patch-list.html b/patchwork/templates/patchwork/partials/patch-list.html index a882cd9d8..efefce912 100644 --- a/patchwork/templates/patchwork/partials/patch-list.html +++ b/patchwork/templates/patchwork/partials/patch-list.html @@ -69,6 +69,7 @@ {% endif %} +{% if not order.editable %} {% if order.name == "name" %} @@ -77,11 +78,10 @@ Patch {% else %} -{% if not order.editable %} Patch +{% endif %} {% else %} Patch -{% endif %} {% endif %} @@ -98,6 +98,7 @@ +{% if not order.editable %} {% if order.name == "date" %} @@ -106,15 +107,15 @@ Date {% else %} -{% if not order.editable %} Date +{% endif %} {% else %} Date -{% endif %} {% endif %} +{% if not order.editable %} {% if order.name == "submitter" %} @@ -123,17 +124,17 @@ Submitter {% else %} -{% if not order.editable %} Submitter +{% endif %} {% else %} Submitter -{% endif %} {% endif %} +{% if not order.editable %} {% if order.name == "delegate" %} @@ -142,15 +143,15 @@ Delegate {% else %} -{% if not order.editable %} Delegate +{% endif %} {% else %} Delegate -{% endif %} {% endif %} +{% if not order.editable %} {% if order.name == "state" %} @@ -159,11 +160,10 @@ State {% else %} -{% if not order.editable %} State +{% endif %} {% else %} State -{% endif %} {% endif %} From 09613eb541a59d9fd64f0e34908a86b9e037f8dd Mon Sep 17 00:00:00 2001 From: Raxel Gutierrez Date: Mon, 23 Aug 2021 18:28:29 +0000 Subject: [PATCH 37/46] views: Clean up patch-list page Add ids to table cells, and rename selectors using hyphen delimited strings to clean up and improve readability of patch-list.html. Also, create a partial template errors.html for errors that render with form submission.These changes make the code healthier, ready for change, and overall more readable. No user-visible change should be noticed. Signed-off-by: Raxel Gutierrez Signed-off-by: Stephen Finucane [stephenfin: Addressed merge conflicts and renamed some Python variables in snake_case also] --- htdocs/css/style.css | 16 +-- htdocs/js/bundle.js | 12 +- .../patchwork/partials/patch-list.html | 108 +++++++++--------- patchwork/templates/patchwork/submission.html | 32 +++--- patchwork/tests/views/test_bundles.py | 22 ++-- patchwork/tests/views/test_patch.py | 10 +- patchwork/views/__init__.py | 6 +- patchwork/views/bundle.py | 2 +- patchwork/views/patch.py | 16 +-- 9 files changed, 112 insertions(+), 112 deletions(-) diff --git a/htdocs/css/style.css b/htdocs/css/style.css index c4025c050..57c52e059 100644 --- a/htdocs/css/style.css +++ b/htdocs/css/style.css @@ -162,25 +162,25 @@ table.pw-list > thead { background-color: white; } -a.colinactive, a.colactive { +a.col-inactive, a.col-active { color: black; text-decoration: none; } -a.colinactive:hover { +a.col-inactive:hover { color: red; } div.filters { } -div.patchforms { +div.patch-forms { margin-top: 1em; } /* list order manipulation */ -table.patchlist tr.draghover { +table.patch-list tr.draghover { background: #e8e8e8 !important; } @@ -256,7 +256,7 @@ table.patch-meta tr th, table.patch-meta tr td { } /* checks forms */ -/* TODO(stephenfin): Merge this with 'div.patchform' rules */ +/* TODO(stephenfin): Merge this with 'div.patch-form' rules */ .checks { border: 1px solid gray; margin: 0.5em 1em; @@ -434,7 +434,7 @@ table.bundlelist td } /* forms that appear for a patch */ -div.patchform { +div.patch-form { border: thin solid #080808; padding-left: 0.6em; padding-right: 0.6em; @@ -442,7 +442,7 @@ div.patchform { margin: 0.5em 5em 0.5em 10px; } -div.patchform h3 { +div.patch-form h3 { margin-top: 0em; margin-left: -0.6em; margin-right: -0.6em; @@ -452,7 +452,7 @@ div.patchform h3 { font-size: 100%; } -div.patchform ul { +div.patch-form ul { list-style-type: none; padding-left: 0.2em; margin-top: 0em; diff --git a/htdocs/js/bundle.js b/htdocs/js/bundle.js index c969d0be4..2a721d0dd 100644 --- a/htdocs/js/bundle.js +++ b/htdocs/js/bundle.js @@ -6,8 +6,8 @@ function order_button_click(node) { var rows, form; - form = $("#reorderform"); - rows = $("#patchlist").get(0).tBodies[0].rows; + form = $("#reorder-form"); + rows = $("#patch-list").get(0).tBodies[0].rows; if (rows.length < 1) return; @@ -35,18 +35,18 @@ function order_button_click(node) $("#reorder\\-cancel").css("display", "inline"); /* show help text */ - $("#reorderhelp").text('Drag & drop rows to reorder'); + $("#reorder-help").text('Drag & drop rows to reorder'); /* enable drag & drop on the patches list */ - $("#patchlist").tableDnD({ + $("#patch-list").tableDnD({ onDragClass: 'dragging', onDragStart: function() { dragging = true; }, onDrop: function() { dragging = false; } }); /* replace zebra striping with hover */ - $("#patchlist tbody tr").css("background", "inherit"); - $("#patchlist tbody tr").hover(drag_hover_in, drag_hover_out); + $("#patch-list tbody tr").css("background", "inherit"); + $("#patch-list tbody tr").hover(drag_hover_in, drag_hover_out); } editing_order = !editing_order; diff --git a/patchwork/templates/patchwork/partials/patch-list.html b/patchwork/templates/patchwork/partials/patch-list.html index efefce912..48b81f3d5 100644 --- a/patchwork/templates/patchwork/partials/patch-list.html +++ b/patchwork/templates/patchwork/partials/patch-list.html @@ -8,14 +8,14 @@ {% include "patchwork/partials/pagination.html" %} {% if order.editable %} - +
    -
    -
    +
    + {% csrf_token %} - + - + @@ -26,7 +26,7 @@ {% if page.paginator.long_page and user.is_authenticated %} @@ -34,15 +34,15 @@ +{% endblock %} + {% include "patchwork/partials/filters.html" %} {% include "patchwork/partials/pagination.html" %} @@ -32,23 +36,6 @@ {% endif %} - -
    {% csrf_token %} From 17726d72f701dd09b65ffef6a306ef63f863a534 Mon Sep 17 00:00:00 2001 From: Raxel Gutierrez Date: Mon, 23 Aug 2021 18:28:31 +0000 Subject: [PATCH 39/46] views: Move and refactor patch-forms Move patch forms in patch-list and detail page to a new template file patch-forms.html and move them to the top of the patch-list page to improve their discoverability. Refactor forms.py, __init__.py, patch.py, and test_bundles.py files so that the shared bundle form in patch-forms.html works for both the patch-list and patch-detail pages. In particular, the changes normalize the behavior of the error and update messages of the patch forms and updates tests to reflect the changes. Overall, these changes make patch forms ready for change and more synchronized in their behavior. More specifically: - Previously patch forms changes were separated between the patch-detail and patch-list pages. Thus, desired changes to the patch forms required changes to patch-list.html, submission.html, and forms.py. So, the most important benefit to this change is that forms.py and patch-forms.html become the two places to adjust the forms to handle form validation and functionality as well as UI changes. - Previously the patch forms in patch-list.html handled error and update messages through views in patch.py, whereas the patch forms in submission.html handled the messages with forms.py. Now, with a single patch forms component in patch-forms.html, forms.py is set to handle the messages and handle form validation for both pages. Signed-off-by: Raxel Gutierrez Signed-off-by: Stephen Finucane [stephenfin: Address merge conflicts] --- patchwork/forms.py | 11 ++- .../patchwork/partials/patch-forms.html | 49 ++++++++++ .../patchwork/partials/patch-list.html | 95 ++----------------- patchwork/templates/patchwork/submission.html | 87 +---------------- patchwork/tests/views/test_bundles.py | 26 ++--- patchwork/views/__init__.py | 84 ++++++++-------- patchwork/views/patch.py | 37 ++------ 7 files changed, 135 insertions(+), 254 deletions(-) create mode 100644 patchwork/templates/patchwork/partials/patch-forms.html diff --git a/patchwork/forms.py b/patchwork/forms.py index ed06d0d15..59e82ba04 100644 --- a/patchwork/forms.py +++ b/patchwork/forms.py @@ -64,7 +64,7 @@ class BundleForm(forms.ModelForm): regex=r'^[^/]+$', min_length=1, max_length=50, - label='Name', + required=False, error_messages={'invalid': "Bundle names can't contain slashes"}, ) @@ -76,12 +76,19 @@ class Meta: class CreateBundleForm(BundleForm): def clean_name(self): name = self.cleaned_data['name'] + if not name: + raise forms.ValidationError( + 'No bundle name was specified', code='invalid' + ) + count = Bundle.objects.filter( owner=self.instance.owner, name=name ).count() if count > 0: raise forms.ValidationError( - 'A bundle called %s already exists' % name + 'A bundle called %(name)s already exists', + code='invalid', + params={'name': name}, ) return name diff --git a/patchwork/templates/patchwork/partials/patch-forms.html b/patchwork/templates/patchwork/partials/patch-forms.html new file mode 100644 index 000000000..80f828153 --- /dev/null +++ b/patchwork/templates/patchwork/partials/patch-forms.html @@ -0,0 +1,49 @@ +
    +{% if patch_form %} +
    +
    + {{ patch_form.state.errors }} + {{ patch_form.state }} +
    +
    + {{ patch_form.delegate.errors }} + {{ patch_form.delegate }} +
    +
    + {{ patch_form.archived.errors }} + {{ patch_form.archived.label_tag }} {{ patch_form.archived }} +
    + +
    +{% endif %} +{% if user.is_authenticated %} +
    +
    + {{ create_bundle_form.name.errors }} + {{ create_bundle_form.name }} + +
    + {% if bundles %} +
    + + +
    + {% endif %} + {% if bundle %} +
    + + +
    + {% endif %} +
    +{% endif %} +
    diff --git a/patchwork/templates/patchwork/partials/patch-list.html b/patchwork/templates/patchwork/partials/patch-list.html index 1bdef36d2..981ceee58 100644 --- a/patchwork/templates/patchwork/partials/patch-list.html +++ b/patchwork/templates/patchwork/partials/patch-list.html @@ -9,7 +9,6 @@ {% endblock %} {% include "patchwork/partials/filters.html" %} -{% include "patchwork/partials/pagination.html" %} {% if order.editable %} @@ -28,18 +27,15 @@
    {% endif %} -{% if page.paginator.long_page and user.is_authenticated %} - -{% endif %} - - + {% csrf_token %} + +
    +{% include "patchwork/partials/patch-forms.html" %} +{% include "patchwork/partials/pagination.html" %} +
    @@ -159,7 +155,7 @@ {% for patch in page.object_list %} - + {% if user.is_authenticated %}
    @@ -201,82 +197,5 @@ {% if page.paginator.count %} {% include "patchwork/partials/pagination.html" %} - -
    - -{% if patch_form %} -
    -

    Properties

    - - - - - - - - - - - - - - - - - -
    Change state: - {{ patch_form.state }} - {{ patch_form.state.errors }} -
    Delegate to: - {{ patch_form.delegate }} - {{ patch_form.delegate.errors }} -
    Archive: - {{ patch_form.archived }} - {{ patch_form.archived.errors }} -
    - -
    -
    -{% endif %} - -{% if user.is_authenticated %} -
    -

    Bundling

    - - - - - -{% if bundles %} - - - - -{% endif %} -{% if bundle %} - - - - -{% endif %} -
    Create bundle: - - -
    Add to bundle: - - -
    Remove from bundle: - - -
    -
    -{% endif %} -
    -
    -
    {% endif %} diff --git a/patchwork/templates/patchwork/submission.html b/patchwork/templates/patchwork/submission.html index aa4e5553d..cd74491c0 100644 --- a/patchwork/templates/patchwork/submission.html +++ b/patchwork/templates/patchwork/submission.html @@ -132,89 +132,10 @@

    {{ submission.name }}

    {% endif %}
    -
    -{% if patch_form %} -
    -

    Patch Properties

    -
    - {% csrf_token %} - - - - - - - - - - - - - - - - - -
    Change state: - {{ patch_form.state }} - {{ patch_form.state.errors }} -
    Delegate to: - {{ patch_form.delegate }} - {{ patch_form.delegate.errors }} -
    Archived: - {{ patch_form.archived }} - {{ patch_form.archived.errors }} -
    - -
    -
    -
    -{% endif %} - -{% if create_bundle_form %} -
    -

    Bundling

    - - - - - -{% if bundles %} - - - - -{% endif %} -
    Create bundle: -{% if create_bundle_form.non_field_errors %} -
    {{create_bundle_form.non_field_errors}}
    -{% endif %} -
    - {% csrf_token %} - -{% if create_bundle_form.name.errors %} -
    {{create_bundle_form.name.errors}}
    -{% endif %} - {{ create_bundle_form.name }} - -
    -
    Add to bundle: -
    -{% csrf_token %} - - - -
    -
    -
    -{% endif %} -
    -
    -
    +
    + {% csrf_token %} + {% include "patchwork/partials/patch-forms.html" %} +
    {% if submission.pull_url %}

    Pull-request

    diff --git a/patchwork/tests/views/test_bundles.py b/patchwork/tests/views/test_bundles.py index 24ebe6123..1d317c305 100644 --- a/patchwork/tests/views/test_bundles.py +++ b/patchwork/tests/views/test_bundles.py @@ -369,7 +369,7 @@ def test_create_empty_bundle(self): newbundlename = 'testbundle-new' params = { 'form': 'patch-list-form', - 'bundle_name': newbundlename, + 'name': newbundlename, 'action': 'Create', 'project': self.project.id, } @@ -389,7 +389,7 @@ def test_create_non_empty_bundle(self): params = { 'form': 'patch-list-form', - 'bundle_name': newbundlename, + 'name': newbundlename, 'action': 'Create', 'project': self.project.id, 'patch_id:%d' % patch.id: 'checked', @@ -418,7 +418,7 @@ def test_create_non_empty_bundle_empty_name(self): params = { 'form': 'patch-list-form', - 'bundle_name': '', + 'name': '', 'action': 'Create', 'project': self.project.id, 'patch_id:%d' % patch.id: 'checked', @@ -444,7 +444,7 @@ def test_create_duplicate_name(self): params = { 'form': 'patch-list-form', - 'bundle_name': newbundlename, + 'name': newbundlename, 'action': 'Create', 'project': self.project.id, 'patch_id:%d' % patch.id: 'checked', @@ -475,7 +475,9 @@ def test_create_duplicate_name(self): ) self.assertNotContains(response, 'Bundle %s created' % newbundlename) - self.assertContains(response, 'You already have a bundle called') + self.assertContains( + response, 'A bundle called %s already exists' % newbundlename + ) self.assertEqual(Bundle.objects.count(), n_bundles) self.assertEqual(bundle.patches.count(), 1) @@ -485,7 +487,7 @@ def test_create_non_empty_bundle(self): newbundlename = 'testbundle-new' patch = self.patches[0] - params = {'name': newbundlename, 'action': 'createbundle'} + params = {'name': newbundlename, 'action': 'Create'} response = self.client.post( reverse( @@ -508,7 +510,7 @@ def test_create_with_existing_name(self): newbundlename = self.bundle.name patch = self.patches[0] - params = {'name': newbundlename, 'action': 'createbundle'} + params = {'name': newbundlename, 'action': 'Create'} response = self.client.post( reverse( @@ -644,7 +646,7 @@ def test_add_new_and_duplicate(self): class BundleAddFromPatchTest(BundleTestBase): def test_add_to_empty_bundle(self): patch = self.patches[0] - params = {'action': 'addtobundle', 'bundle_id': self.bundle.id} + params = {'action': 'Add', 'bundle_id': self.bundle.id} response = self.client.post( reverse( @@ -659,7 +661,7 @@ def test_add_to_empty_bundle(self): self.assertContains( response, - 'added to bundle "%s"' % self.bundle.name, + 'added to bundle %s' % self.bundle.name, count=1, ) @@ -669,7 +671,7 @@ def test_add_to_empty_bundle(self): def test_add_to_non_empty_bundle(self): self.bundle.append_patch(self.patches[0]) patch = self.patches[1] - params = {'action': 'addtobundle', 'bundle_id': self.bundle.id} + params = {'action': 'Add', 'bundle_id': self.bundle.id} response = self.client.post( reverse( @@ -684,7 +686,7 @@ def test_add_to_non_empty_bundle(self): self.assertContains( response, - 'added to bundle "%s"' % self.bundle.name, + 'added to bundle %s' % self.bundle.name, count=1, ) @@ -724,7 +726,7 @@ def _test_order(self, ids, expected_order): # need to define our querystring explicity to enforce ordering params = { 'form': 'patch-list-form', - 'bundle_name': newbundlename, + 'name': newbundlename, 'action': 'Create', 'project': self.project.id, } diff --git a/patchwork/views/__init__.py b/patchwork/views/__init__.py index 51649488f..db484c799 100644 --- a/patchwork/views/__init__.py +++ b/patchwork/views/__init__.py @@ -3,11 +3,14 @@ # # SPDX-License-Identifier: GPL-2.0-or-later +import json + from django.contrib import messages from django.shortcuts import get_object_or_404 from django.db.models import Prefetch from patchwork.filters import Filters +from patchwork.forms import CreateBundleForm from patchwork.forms import MultiplePatchForm from patchwork.models import Bundle from patchwork.models import BundlePatch @@ -108,52 +111,34 @@ def apply(self, qs): # TODO(stephenfin): Refactor this to break it into multiple, testable functions -def set_bundle(request, project, action, data, patches, context): +def set_bundle(request, project, action, data, patches): # set up the bundle bundle = None user = request.user if action == 'create': - bundle_name = data['bundle_name'].strip() - if '/' in bundle_name: - return ["Bundle names can't contain slashes"] - - if not bundle_name: - return ['No bundle name was specified'] - - if Bundle.objects.filter(owner=user, name=bundle_name).count() > 0: - return ['You already have a bundle called "%s"' % bundle_name] - + bundle_name = data['name'].strip() bundle = Bundle(owner=user, project=project, name=bundle_name) - bundle.save() - messages.success(request, 'Bundle %s created' % bundle.name) + create_bundle_form = CreateBundleForm( + instance=bundle, data=request.POST + ) + if create_bundle_form.is_valid(): + create_bundle_form.save() + add_bundle_patches(request, patches, bundle) + bundle.save() + messages.success(request, 'Bundle %s created' % bundle.name) + else: + formErrors = json.loads(create_bundle_form.errors.as_json()) + errors = [e['message'] for e in formErrors['name']] + return errors elif action == 'add': + if not data['bundle_id']: + return ['No bundle was selected'] bundle = get_object_or_404(Bundle, id=data['bundle_id']) + add_bundle_patches(request, patches, bundle) elif action == 'remove': bundle = get_object_or_404(Bundle, id=data['removed_bundle_id']) - - if not bundle: - return ['no such bundle'] - - for patch in patches: - if action in ['create', 'add']: - bundlepatch_count = BundlePatch.objects.filter( - bundle=bundle, patch=patch - ).count() - if bundlepatch_count == 0: - bundle.append_patch(patch) - messages.success( - request, - "Patch '%s' added to bundle %s" - % (patch.name, bundle.name), - ) - else: - messages.warning( - request, - "Patch '%s' already in bundle %s" - % (patch.name, bundle.name), - ) - elif action == 'remove': + for patch in patches: try: bp = BundlePatch.objects.get(bundle=bundle, patch=patch) bp.delete() @@ -165,10 +150,26 @@ def set_bundle(request, project, action, data, patches, context): "Patch '%s' removed from bundle %s\n" % (patch.name, bundle.name), ) + return [] - bundle.save() - return [] +def add_bundle_patches(request, patches, bundle): + for patch in patches: + bundlepatch_count = BundlePatch.objects.filter( + bundle=bundle, patch=patch + ).count() + if bundlepatch_count == 0: + bundle.append_patch(patch) + bundle.save() + messages.success( + request, + "Patch '%s' added to bundle %s" % (patch.name, bundle.name), + ) + else: + messages.warning( + request, + "Patch '%s' already in bundle %s" % (patch.name, bundle.name), + ) def generic_list( @@ -232,6 +233,7 @@ def generic_list( data = None user = request.user properties_form = None + create_bundle_form = None if user.is_authenticated: # we only pass the post data to the MultiplePatchForm if that was @@ -241,19 +243,20 @@ def generic_list( data_tmp = data properties_form = MultiplePatchForm(project, data=data_tmp) + create_bundle_form = CreateBundleForm() if request.method == 'POST' and data.get('form') == 'patch-list-form': action = data.get('action', '').lower() # special case: the user may have hit enter in the 'create bundle' # text field, so if non-empty, assume the create action: - if data.get('bundle_name', False): + if data.get('name', False): action = 'create' ps = Patch.objects.filter(id__in=get_patch_ids(data)) if action in bundle_actions: - errors = set_bundle(request, project, action, data, ps, context) + errors = set_bundle(request, project, action, data, ps) elif properties_form and action == properties_form.action: errors = process_multiplepatch_form( @@ -320,6 +323,7 @@ def generic_list( { 'page': paginator.current_page, 'patch_form': properties_form, + 'create_bundle_form': create_bundle_form, 'project': project, 'order': order, } diff --git a/patchwork/views/patch.py b/patchwork/views/patch.py index 838391635..efe94f17c 100644 --- a/patchwork/views/patch.py +++ b/patchwork/views/patch.py @@ -14,11 +14,11 @@ from patchwork.forms import CreateBundleForm from patchwork.forms import PatchForm -from patchwork.models import Bundle from patchwork.models import Cover from patchwork.models import Patch from patchwork.models import Project from patchwork.views import generic_list +from patchwork.views import set_bundle from patchwork.views.utils import patch_to_mbox from patchwork.views.utils import series_patch_to_mbox @@ -64,6 +64,7 @@ def patch_detail(request, project_id, msgid): form = None create_bundle_form = None + errors = None if editable: form = PatchForm(instance=patch) @@ -75,39 +76,15 @@ def patch_detail(request, project_id, msgid): if action: action = action.lower() - if action == 'createbundle': - bundle = Bundle(owner=request.user, project=project) - create_bundle_form = CreateBundleForm( - instance=bundle, data=request.POST + if action in ['create', 'add']: + errors = set_bundle( + request, project, action, request.POST, [patch] ) - if create_bundle_form.is_valid(): - create_bundle_form.save() - bundle.append_patch(patch) - bundle.save() - create_bundle_form = CreateBundleForm() - messages.success(request, 'Bundle %s created' % bundle.name) - elif action == 'addtobundle': - bundle = get_object_or_404( - Bundle, id=request.POST.get('bundle_id') - ) - if bundle.append_patch(patch): - messages.success( - request, - 'Patch "%s" added to bundle "%s"' - % (patch.name, bundle.name), - ) - else: - messages.error( - request, - 'Failed to add patch "%s" to bundle "%s": ' - 'patch is already in bundle' % (patch.name, bundle.name), - ) - # all other actions require edit privs elif not editable: return HttpResponseForbidden() - elif action is None: + elif action == 'update': form = PatchForm(data=request.POST, instance=patch) if form.is_valid(): form.save() @@ -147,6 +124,8 @@ def patch_detail(request, project_id, msgid): context['project'] = patch.project context['related_same_project'] = related_same_project context['related_different_project'] = related_different_project + if errors: + context['errors'] = errors return render(request, 'patchwork/submission.html', context) From 8c7aed3263d1a42083529f7b65797bb903399ceb Mon Sep 17 00:00:00 2001 From: Raxel Gutierrez Date: Mon, 23 Aug 2021 18:28:32 +0000 Subject: [PATCH 40/46] views: Style modification forms as an action bar Add styling to the new patch list html code to make the change property and bundle action forms more usable. Before [1] and after [2] images for reference. [1] https://i.imgur.com/Pzelipp.png [2] https://i.imgur.com/UtNJXuf.png Signed-off-by: Raxel Gutierrez Signed-off-by: Stephen Finucane [stephenfin: Addressed merge conflicts, tweak CSS slightly] --- htdocs/css/style.css | 73 ++++++++++++++++++++++++++++++++++++-------- patchwork/forms.py | 42 ++++++++++++++++++++----- 2 files changed, 94 insertions(+), 21 deletions(-) diff --git a/htdocs/css/style.css b/htdocs/css/style.css index 57c52e059..268a8c372 100644 --- a/htdocs/css/style.css +++ b/htdocs/css/style.css @@ -174,10 +174,6 @@ a.col-inactive:hover { div.filters { } -div.patch-forms { - margin-top: 1em; -} - /* list order manipulation */ table.patch-list tr.draghover { @@ -201,7 +197,7 @@ input#reorder-change { .paginator { text-align: right; clear: both; - margin: 8px 0 15px; + margin: 16px 0; } .paginator .prev-na, @@ -433,13 +429,62 @@ table.bundlelist td padding-right: 2em; } +.patch-list-actions { + width: 100%; + display: inline-flex; + flex-wrap: wrap; + justify-content: space-between; +} + /* forms that appear for a patch */ +.patch-forms { + display: inline-flex; + flex-wrap: wrap; + margin: 16px 0; +} + div.patch-form { - border: thin solid #080808; - padding-left: 0.6em; - padding-right: 0.6em; - float: left; - margin: 0.5em 5em 0.5em 10px; + display: flex; + flex-wrap: wrap; + align-items: center; +} + +select[class^=change-property-], .archive-patch-select, .add-bundle { + padding: 4px; + margin-right: 8px; + box-sizing: border-box; + border-radius: 4px; + background-color: var(--light-color); +} + +#patch-form-archive { + display: flex; + align-items: center; + margin-right: 4px; +} + +#patch-form-archive > label { + margin: 0px; +} + +#patch-form-archive > select, #patch-form-archive > input { + margin: 0px 4px 0px 4px; +} + +.patch-form-submit { + font-weight: bold; + padding: 4px; +} + +#patch-form-bundle, #add-to-bundle, #remove-bundle { + margin-left: 16px; +} + +.create-bundle { + padding: 4px; + margin-right: 8px; + box-sizing: border-box; + border-radius: 4px; } div.patch-form h3 { @@ -458,15 +503,17 @@ div.patch-form ul { margin-top: 0em; } -/* forms */ -table.form { +.create-bundle { + padding: 4px; + margin-right: 8px; + box-sizing: border-box; + border-radius: 4px; } span.help_text { font-size: 80%; } - table.form td { padding: 0.6em; vertical-align: top; diff --git a/patchwork/forms.py b/patchwork/forms.py index 59e82ba04..cf77bdcc4 100644 --- a/patchwork/forms.py +++ b/patchwork/forms.py @@ -66,6 +66,9 @@ class BundleForm(forms.ModelForm): max_length=50, required=False, error_messages={'invalid': "Bundle names can't contain slashes"}, + widget=forms.TextInput( + attrs={'class': 'create-bundle', 'placeholder': 'Bundle name'} + ), ) class Meta: @@ -136,19 +139,29 @@ class PatchForm(forms.ModelForm): def __init__(self, instance=None, project=None, *args, **kwargs): super(PatchForm, self).__init__(instance=instance, *args, **kwargs) self.fields['delegate'] = forms.ModelChoiceField( - queryset=_get_delegate_qs(project, instance), required=False + queryset=_get_delegate_qs(project, instance), + widget=forms.Select(attrs={'class': 'change-property-delegate'}), + required=False, ) class Meta: model = Patch fields = ['state', 'archived', 'delegate'] + widgets = { + 'state': forms.Select(attrs={'class': 'change-property-state'}), + 'archived': forms.CheckboxInput( + attrs={'class': 'archive-patch-check'} + ), + } class OptionalModelChoiceField(forms.ModelChoiceField): - no_change_choice = ('*', 'no change') + no_change_choice = ('*', 'No change') to_field_name = None - def __init__(self, *args, **kwargs): + def __init__(self, *args, placeholder, className, **kwargs): + self.no_change_choice = ('*', placeholder) + self.widget = forms.Select(attrs={'class': className}) super(OptionalModelChoiceField, self).__init__( initial=self.no_change_choice[0], *args, **kwargs ) @@ -181,6 +194,10 @@ def clean(self, value): class OptionalBooleanField(forms.TypedChoiceField): + def __init__(self, className, *args, **kwargs): + self.widget = forms.Select(attrs={'class': className}) + super(OptionalBooleanField, self).__init__(*args, **kwargs) + def is_no_change(self, value): return value == self.empty_value @@ -188,22 +205,31 @@ def is_no_change(self, value): class MultiplePatchForm(forms.Form): action = 'update' archived = OptionalBooleanField( + className='archive-patch-select', choices=[ - ('*', 'no change'), - ('True', 'Archived'), - ('False', 'Unarchived'), + ('*', 'No change'), + ('True', 'Archive'), + ('False', 'Unarchive'), ], coerce=lambda x: x == 'True', empty_value='*', + label='Archived', ) def __init__(self, project, *args, **kwargs): super(MultiplePatchForm, self).__init__(*args, **kwargs) self.fields['delegate'] = OptionalModelChoiceField( - queryset=_get_delegate_qs(project=project), required=False + queryset=_get_delegate_qs(project=project), + placeholder='Delegate to', + className='change-property-delegate', + label='Delegate to', + required=False, ) self.fields['state'] = OptionalModelChoiceField( - queryset=State.objects.all() + queryset=State.objects.all(), + placeholder='Change state', + className='change-property-state', + label='Change state', ) def save(self, instance, commit=True): From a2cac80ec97b64c0090fc1b6f8eae52ac73af20a Mon Sep 17 00:00:00 2001 From: andrepapoti Date: Mon, 18 Mar 2024 11:34:26 -0300 Subject: [PATCH 41/46] models: Add 'related_series' field to Series model Closes #506 Signed-off-by: andrepapoti --- .../migrations/0048_series_related_series.py | 17 +++++++++++++++++ patchwork/models.py | 15 +++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 patchwork/migrations/0048_series_related_series.py diff --git a/patchwork/migrations/0048_series_related_series.py b/patchwork/migrations/0048_series_related_series.py new file mode 100644 index 000000000..401345a2a --- /dev/null +++ b/patchwork/migrations/0048_series_related_series.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.4 on 2024-11-04 04:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('patchwork', '0047_add_database_indexes'), + ] + + operations = [ + migrations.AddField( + model_name='series', + name='related_series', + field=models.ManyToManyField(to='patchwork.series'), + ), + ] diff --git a/patchwork/models.py b/patchwork/models.py index a05db7f9c..28183e7d1 100644 --- a/patchwork/models.py +++ b/patchwork/models.py @@ -848,6 +848,7 @@ class Series(FilenameMixin, models.Model): help_text='An optional name to associate with ' 'the series, e.g. "John\'s PCI series".', ) + related_series = models.ManyToManyField('self') date = models.DateTimeField() submitter = models.ForeignKey(Person, on_delete=models.CASCADE) version = models.IntegerField( @@ -934,6 +935,20 @@ def add_patch(self, patch, number): return patch + def is_editable(self, user): + if not user.is_authenticated: + return False + + if user.is_superuser: + return True + + try: + person = Person.objects.get(user=user) + except Exception: + return False + + return person == self.submitter + def get_absolute_url(self): # TODO(stephenfin): We really need a proper series view return reverse( From 6e3d250e15a68317b05146f3b508119fa1a1b1fb Mon Sep 17 00:00:00 2001 From: andrepapoti Date: Mon, 18 Mar 2024 11:37:21 -0300 Subject: [PATCH 42/46] serializers: Add 'related_versions' field to series serializer For two series to be linked togheter they must belong to the same project Closes #506 Signed-off-by: andrepapoti --- patchwork/api/series.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/patchwork/api/series.py b/patchwork/api/series.py index b88ed1f5f..ab44ee701 100644 --- a/patchwork/api/series.py +++ b/patchwork/api/series.py @@ -5,7 +5,9 @@ from rest_framework.generics import ListAPIView from rest_framework.generics import RetrieveAPIView +from rest_framework.generics import UpdateAPIView from rest_framework.serializers import SerializerMethodField +from rest_framework.exceptions import ValidationError from patchwork.api.base import BaseHyperlinkedModelSerializer from patchwork.api.base import PatchworkPermission @@ -14,6 +16,7 @@ from patchwork.api.embedded import PatchSerializer from patchwork.api.embedded import PersonSerializer from patchwork.api.embedded import ProjectSerializer +from patchwork.api.embedded import SeriesSerializer as RelatedSeriesSerializer from patchwork.models import Series @@ -24,6 +27,14 @@ class SeriesSerializer(BaseHyperlinkedModelSerializer): mbox = SerializerMethodField() cover_letter = CoverSerializer(read_only=True) patches = PatchSerializer(read_only=True, many=True) + related_series = RelatedSeriesSerializer(many=True) + + def get_related_series(self, obj): + urls = [] + for related_series in obj.related_series.all(): + url = self.get_web_url(related_series) + urls.append(url) + return urls def get_web_url(self, instance): request = self.context.get('request') @@ -33,6 +44,16 @@ def get_mbox(self, instance): request = self.context.get('request') return request.build_absolute_uri(instance.get_mbox_url()) + def validate_related_series(self, related_series): + for series in related_series: + if self.instance.id == series.id: + raise ValidationError('A series cannot be linked to itself.') + if self.instance.project.id != series.project.id: + raise ValidationError( + 'Series must belong to the same project.' + ) + return related_series + class Meta: model = Series fields = ( @@ -44,6 +65,7 @@ class Meta: 'date', 'submitter', 'version', + 'related_series', 'total', 'received_total', 'received_all', @@ -90,7 +112,7 @@ class SeriesList(SeriesMixin, ListAPIView): ordering = 'id' -class SeriesDetail(SeriesMixin, RetrieveAPIView): - """Show a series.""" +class SeriesDetail(SeriesMixin, RetrieveAPIView, UpdateAPIView): + """Show and update a series.""" pass From 14078c5412c5c22ded0a21b776adc8e712cd518a Mon Sep 17 00:00:00 2001 From: andrepapoti Date: Mon, 18 Mar 2024 14:42:29 -0300 Subject: [PATCH 43/46] serializers: Optimize query performance on SeriesMixin for 'related_series' Ensure the retrival of series is keept at O(1) complexity. Closes #506 Signed-off-by: andrepapoti --- patchwork/api/series.py | 6 +++++- patchwork/tests/api/test_series.py | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/patchwork/api/series.py b/patchwork/api/series.py index ab44ee701..0c22b3c10 100644 --- a/patchwork/api/series.py +++ b/patchwork/api/series.py @@ -98,7 +98,11 @@ class SeriesMixin(object): def get_queryset(self): return ( Series.objects.all() - .prefetch_related('patches__project', 'cover_letter__project') + .prefetch_related( + 'patches__project', + 'cover_letter__project', + 'related_series__project', + ) .select_related('submitter', 'project') ) diff --git a/patchwork/tests/api/test_series.py b/patchwork/tests/api/test_series.py index 730678a84..597b61a94 100644 --- a/patchwork/tests/api/test_series.py +++ b/patchwork/tests/api/test_series.py @@ -152,7 +152,7 @@ def test_list_bug_335(self): create_cover(series=series_obj) create_patch(series=series_obj) - with self.assertNumQueries(6): + with self.assertNumQueries(7): self.client.get(self.api_url()) @utils.store_samples('series-detail') From e9c5545fedb58b98700b8289352967f7ebfad10d Mon Sep 17 00:00:00 2001 From: andrepapoti Date: Mon, 18 Mar 2024 11:40:25 -0300 Subject: [PATCH 44/46] urls: Create endpoint to link two series Closes #506 Signed-off-by: andrepapoti --- docs/api/schemas/generate-schemas.py | 4 +- docs/api/schemas/latest/patchwork.yaml | 41 +- docs/api/schemas/patchwork.j2 | 43 + docs/api/schemas/v1.4/patchwork.yaml | 3265 ++++++++++++++++++++++++ 4 files changed, 3350 insertions(+), 3 deletions(-) create mode 100644 docs/api/schemas/v1.4/patchwork.yaml diff --git a/docs/api/schemas/generate-schemas.py b/docs/api/schemas/generate-schemas.py index 14b741473..52008dffd 100755 --- a/docs/api/schemas/generate-schemas.py +++ b/docs/api/schemas/generate-schemas.py @@ -14,8 +14,8 @@ yaml = None ROOT_DIR = os.path.dirname(os.path.realpath(__file__)) -VERSIONS = [(1, 0), (1, 1), (1, 2), (1, 3), None] -LATEST_VERSION = (1, 3) +VERSIONS = [(1, 0), (1, 1), (1, 2), (1, 3), (1, 4), None] +LATEST_VERSION = (1, 4) def generate_schemas(): diff --git a/docs/api/schemas/latest/patchwork.yaml b/docs/api/schemas/latest/patchwork.yaml index 93e56fa0f..447b8aa86 100644 --- a/docs/api/schemas/latest/patchwork.yaml +++ b/docs/api/schemas/latest/patchwork.yaml @@ -13,7 +13,7 @@ info: license: name: GPL v2 License url: https://www.gnu.org/licenses/gpl-2.0.html - version: '1.3' + version: '1.4' paths: /api: get: @@ -1223,6 +1223,38 @@ paths: $ref: '#/components/schemas/Error' tags: - series + patch: + summary: Link both series. + description: | + Apply a partial update to a Series + operationId: series_link + responses: + '200': + description: 'Updated series' + content: + application/json: + schema: + $ref: '#/components/schemas/Series' + '400': + description: 'Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - series /api/users: get: summary: List users. @@ -2605,6 +2637,13 @@ components: $ref: '#/components/schemas/PatchEmbedded' readOnly: true uniqueItems: true + related_series: + title: Related series + type: array + items: + $ref: '#/components/schemas/Series' + readOnly: true + uniqueItems: true User: type: object title: User diff --git a/docs/api/schemas/patchwork.j2 b/docs/api/schemas/patchwork.j2 index 516fbe88d..5348b5f08 100644 --- a/docs/api/schemas/patchwork.j2 +++ b/docs/api/schemas/patchwork.j2 @@ -1248,6 +1248,40 @@ paths: $ref: '#/components/schemas/Error' tags: - series +{% if version >= (1, 4) %} + patch: + summary: Link both series. + description: | + Apply a partial update to a Series + operationId: series_link + responses: + '200': + description: 'Updated series' + content: + application/json: + schema: + $ref: '#/components/schemas/Series' + '400': + description: 'Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - series +{% endif %} /api/{{ version_url }}users: get: summary: List users. @@ -2699,6 +2733,15 @@ components: $ref: '#/components/schemas/PatchEmbedded' readOnly: true uniqueItems: true +{% if version >= (1, 4) %} + related_series: + title: Related series + type: array + items: + $ref: '#/components/schemas/Series' + readOnly: true + uniqueItems: true +{% endif %} User: type: object title: User diff --git a/docs/api/schemas/v1.4/patchwork.yaml b/docs/api/schemas/v1.4/patchwork.yaml new file mode 100644 index 000000000..2da59db70 --- /dev/null +++ b/docs/api/schemas/v1.4/patchwork.yaml @@ -0,0 +1,3265 @@ +# DO NOT EDIT THIS FILE. It is generated from a template. Changes should be +# proposed against the template and updated files generated using the +# 'generate-schemas.py' tool +--- +openapi: '3.1.0' +info: + title: Patchwork API + description: | + Patchwork is a web-based patch tracking system designed to facilitate the + contribution and management of contributions to an open-source project. + contact: + email: patchwork@lists.ozlabs.org + license: + name: GPL v2 License + url: https://www.gnu.org/licenses/gpl-2.0.html + version: '1.4' +paths: + /api/1.4/: + get: + summary: List API resources. + description: | + Show paths to all supported API resources. + operationId: api_list + parameters: [] + responses: + '200': + description: 'List of API resources' + content: + application/json: + schema: + $ref: '#/components/schemas/Index' + tags: + - api + /api/1.4/bundles: + get: + summary: List bundles. + description: | + List all bundles that the current user has access to. + For unauthenticated requests, only public bundles can be shown. + operationId: bundles_list + parameters: + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PageSize' + - $ref: '#/components/parameters/Order' + - $ref: '#/components/parameters/Search' + - in: query + name: project + description: An ID or linkname of a project to filter bundles by. + schema: + title: '' + type: string + - in: query + name: owner + description: An ID or username of a user to filter bundles by. + schema: + title: '' + type: string + - in: query + name: public + description: Show only public (`true`) or private (`false`) bundles. + schema: + title: '' + type: string + enum: + - 'true' + - 'false' + responses: + '200': + description: 'List of bundles' + headers: + Link: + $ref: '#/components/headers/Link' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Bundle' + tags: + - bundles + post: + summary: Create a bundle. + description: | + Create a new bundle. + operationId: bundles_create + security: + - basicAuth: [] + - apiKeyAuth: [] + requestBody: + $ref: '#/components/requestBodies/Bundle' + responses: + '201': + description: 'Created bundle' + content: + application/json: + schema: + $ref: '#/components/schemas/Bundle' + '400': + description: 'Invalid request' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorBundleCreateUpdate' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - bundles + /api/1.4/bundles/{id}: + parameters: + - in: path + name: id + required: true + description: A unique integer value identifying this bundle. + schema: + title: ID + type: integer + get: + summary: Show a bundle. + description: | + Retrieve a bundle by its ID. + The bundle must be either be public or be owned by the currently authenticated user. + operationId: bundles_read + responses: + '200': + description: 'A bundle' + content: + application/json: + schema: + $ref: '#/components/schemas/Bundle' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - bundles + patch: + summary: Update a bundle (partial). + description: + Partially update an existing bundle. + The bundle must be owned by the currently authenticated user. + operationId: bundles_partial_update + security: + - basicAuth: [] + - apiKeyAuth: [] + requestBody: + $ref: '#/components/requestBodies/Bundle' + responses: + '200': + description: 'Updated bundle' + content: + application/json: + schema: + $ref: '#/components/schemas/Bundle' + '400': + description: 'Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorBundleCreateUpdate' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - bundles + put: + summary: Update a bundle. + description: + Update an existing bundle. + The bundle must be owned by the currently authenticated user. + operationId: bundles_update + security: + - basicAuth: [] + - apiKeyAuth: [] + requestBody: + $ref: '#/components/requestBodies/Bundle' + responses: + '200': + description: 'Updated bundle' + content: + application/json: + schema: + $ref: '#/components/schemas/Bundle' + '400': + description: 'Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorBundleCreateUpdate' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - bundles + /api/1.4/covers: + get: + summary: List cover letters. + description: | + List all cover letters. + operationId: covers_list + parameters: + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PageSize' + - $ref: '#/components/parameters/Order' + - $ref: '#/components/parameters/Search' + - $ref: '#/components/parameters/BeforeFilter' + - $ref: '#/components/parameters/SinceFilter' + - in: query + name: project + description: | + An ID or linkname of a project to filter cover letters by. + schema: + title: '' + type: string + - in: query + name: series + description: An ID of a series to filter cover letters by. + schema: + title: '' + type: string + - in: query + name: submitter + description: | + An ID or email address of a person to filter cover letters by. + schema: + title: '' + type: string + - in: query + name: msgid + description: | + The cover message-id as a case-sensitive string, without leading or + trailing angle brackets, to filter by. + schema: + title: '' + type: string + responses: + '200': + description: 'List of cover letters' + headers: + Link: + $ref: '#/components/headers/Link' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CoverList' + tags: + - covers + /api/1.4/covers/{id}: + parameters: + - in: path + name: id + description: A unique integer value identifying this cover letter. + required: true + schema: + title: ID + type: integer + get: + summary: Show a cover letter. + description: | + Retrieve a cover letter by its ID. + operationId: covers_read + responses: + '200': + description: 'A cover letter' + content: + application/json: + schema: + $ref: '#/components/schemas/CoverDetail' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - covers + /api/1.4/covers/{id}/comments: + parameters: + - in: path + name: id + description: | + A unique integer value identifying the parent cover letter. + required: true + schema: + title: ID + type: integer + get: + summary: List cover letter comments + description: | + List all comments for the given cover letter. + operationId: cover_comments_list + parameters: + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PageSize' + - $ref: '#/components/parameters/Order' + - $ref: '#/components/parameters/Search' + responses: + '200': + description: 'List of comments' + headers: + Link: + $ref: '#/components/headers/Link' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Comment' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - comments + /api/1.4/covers/{cover_id}/comments/{comment_id}: + parameters: + - in: path + name: cover_id + description: A unique integer value identifying the parent cover. + required: true + schema: + title: Cover ID + type: integer + - in: path + name: comment_id + description: A unique integer value identifying this comment. + required: true + schema: + title: Comment ID + type: integer + get: + summary: Show a cover letter comment. + description: | + Retrieve a cover letter comment by its ID. + operationId: cover_comments_read + responses: + '200': + description: 'A cover letter comment' + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - comments + patch: + summary: Update a cover letter comment (partial). + description: + Partially update an existing cover letter comment. + You must be a maintainer of the project that the cover letter comment belongs to. + operationId: cover_comments_partial_update + requestBody: + $ref: '#/components/requestBodies/Comment' + responses: + '200': + description: 'Updated cover letter comment' + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + description: 'Invalid request' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorCommentUpdate' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - comments + /api/1.4/events: + get: + summary: List events. + description: | + List all events. + This list can be quite large. You are encouraged to use filters to narrow it to specific categories or project(s). + operationId: events_list + parameters: + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PageSize' + - $ref: '#/components/parameters/Order' + - $ref: '#/components/parameters/Search' + - $ref: '#/components/parameters/BeforeFilter' + - $ref: '#/components/parameters/SinceFilter' + - in: query + name: project + description: An ID or linkname of a project to filter events by. + schema: + title: '' + type: string + - in: query + name: category + description: | + An event category to filter events by. These categories are subject + to change depending on the version of Patchwork deployed and are + not subject to the versionining constraints present across the rest + of the API. + schema: + title: '' + type: string + enum: + - cover-created + - patch-created + - patch-completed + - patch-state-changed + - patch-relation-changed + - patch-delegated + - check-created + - series-created + - series-completed + - cover-comment-created + - patch-comment-created + - in: query + name: series + description: An ID of a series to filter events by. + schema: + title: '' + type: integer + - in: query + name: patch + description: An ID of a patch to filter events by. + schema: + title: '' + type: integer + - in: query + name: cover + description: An ID of a cover letter to filter events by. + schema: + title: '' + type: integer + responses: + '200': + description: 'List of events' + headers: + Link: + $ref: '#/components/headers/Link' + content: + application/json: + schema: + type: array + items: + anyOf: + - $ref: '#/components/schemas/EventCoverCreated' + - $ref: '#/components/schemas/EventPatchCreated' + - $ref: '#/components/schemas/EventPatchCompleted' + - $ref: '#/components/schemas/EventPatchStateChanged' + - $ref: '#/components/schemas/EventPatchRelationChanged' + - $ref: '#/components/schemas/EventPatchDelegated' + - $ref: '#/components/schemas/EventCheckCreated' + - $ref: '#/components/schemas/EventSeriesCreated' + - $ref: '#/components/schemas/EventSeriesCompleted' + - $ref: '#/components/schemas/EventCoverCommentCreated' + - $ref: '#/components/schemas/EventPatchCommentCreated' + discriminator: + propertyName: category + mapping: + cover-created: '#/components/schemas/EventCoverCreated' + patch-created: '#/components/schemas/EventPatchCreated' + patch-completed: '#/components/schemas/EventPatchCompleted' + patch-state-changed: '#/components/schemas/EventPatchStateChanged' + patch-relation-changed: '#/components/schemas/EventPatchRelationChanged' + patch-delegated: '#/components/schemas/EventPatchDelegated' + check-created: '#/components/schemas/EventCheckCreated' + series-created: '#/components/schemas/EventSeriesCreated' + series-completed: '#/components/schemas/EventSeriesCompleted' + cover-comment-created: '#/components/schemas/EventCoverCommentCreated' + patch-comment-created: '#/components/schemas/EventPatchCommentCreated' + tags: + - events + /api/1.4/patches: + get: + summary: List patches. + description: | + List all patches. + operationId: patches_list + parameters: + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PageSize' + - $ref: '#/components/parameters/Order' + - $ref: '#/components/parameters/Search' + - $ref: '#/components/parameters/BeforeFilter' + - $ref: '#/components/parameters/SinceFilter' + - in: query + name: project + description: An ID or linkname of a project to filter patches by. + schema: + title: '' + type: string + - in: query + name: series + description: An ID of a series to filter patches by. + schema: + title: '' + type: integer + - in: query + name: submitter + description: | + An ID or email address of a person to filter patches by. + schema: + title: '' + type: string + - in: query + name: delegate + description: An ID or username of a user to filter patches by. + schema: + title: '' + type: string + - in: query + name: state + description: A slug representation of a state to filter patches by. + schema: + title: '' + type: string + - in: query + name: archived + description: | + Show only archived (`true`) or non-archived (`false`) patches. + schema: + title: '' + type: string + enum: + - 'true' + - 'false' + - in: query + name: hash + description: | + The patch hash as a case-insensitive hexadecimal string, to filter by. + schema: + title: '' + type: string + - in: query + name: msgid + description: | + The patch message-id as a case-sensitive string, without leading or + trailing angle brackets, to filter by. + schema: + title: '' + type: string + responses: + '200': + description: 'List of patches' + headers: + Link: + $ref: '#/components/headers/Link' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PatchList' + tags: + - patches + /api/1.4/patches/{id}: + parameters: + - in: path + name: id + description: A unique integer value identifying this patch. + required: true + schema: + title: ID + type: integer + get: + summary: Show a patch. + description: | + Retrieve a patch by its ID. + operationId: patches_read + responses: + '200': + description: 'A patch' + content: + application/json: + schema: + $ref: '#/components/schemas/PatchDetail' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - patches + patch: + summary: Update a patch (partial). + description: + Partially update an existing patch. + You must be a maintainer of the project that the patch belongs to. + operationId: patches_partial_update + security: + - basicAuth: [] + - apiKeyAuth: [] + requestBody: + $ref: '#/components/requestBodies/Patch' + responses: + '200': + description: 'An updated patch' + content: + application/json: + schema: + $ref: '#/components/schemas/PatchDetail' + '400': + description: 'Invalid request' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorPatchUpdate' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '409': + description: 'Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - patches + put: + description: Update a patch. + operationId: patches_update + security: + - basicAuth: [] + - apiKeyAuth: [] + requestBody: + $ref: '#/components/requestBodies/Patch' + responses: + '200': + description: 'An updated patch' + content: + application/json: + schema: + $ref: '#/components/schemas/PatchDetail' + '400': + description: 'Invalid request' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorPatchUpdate' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '409': + description: 'Conflict' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - patches + /api/1.4/patches/{id}/comments: + parameters: + - in: path + name: id + description: A unique integer value identifying the parent patch. + required: true + schema: + title: ID + type: integer + get: + summary: List patch comments + description: | + List all comments for the given patch. + operationId: patch_comments_list + parameters: + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PageSize' + - $ref: '#/components/parameters/Order' + - $ref: '#/components/parameters/Search' + responses: + '200': + description: 'List of comments' + headers: + Link: + $ref: '#/components/headers/Link' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Comment' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - comments + /api/1.4/patches/{patch_id}/comments/{comment_id}: + parameters: + - in: path + name: patch_id + description: A unique integer value identifying the parent patch. + required: true + schema: + title: Patch ID + type: integer + - in: path + name: comment_id + description: A unique integer value identifying this comment. + required: true + schema: + title: Comment ID + type: integer + get: + summary: Show a patch comment. + description: | + Retrieve a patch comment by its ID and the ID of the patch. + operationId: patch_comments_read + responses: + '200': + description: 'A patch comment' + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - comments + patch: + summary: Update a patch comment (partial). + description: + Partially update an existing patch comment. + You must be a maintainer of the project that the patch comment belongs to. + operationId: patch_comments_partial_update + requestBody: + $ref: '#/components/requestBodies/Comment' + responses: + '200': + description: 'Updated patch' + content: + application/json: + schema: + $ref: '#/components/schemas/Comment' + '400': + description: 'Invalid request' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorCommentUpdate' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - comments + /api/1.4/patches/{patch_id}/checks: + parameters: + - in: path + name: patch_id + description: A unique integer value identifying the parent patch. + required: true + schema: + title: Patch ID + type: integer + get: + summary: List checks. + description: | + List all checks for the given patch. + operationId: checks_list + parameters: + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PageSize' + - $ref: '#/components/parameters/Order' + - $ref: '#/components/parameters/Search' + - $ref: '#/components/parameters/BeforeFilter' + - $ref: '#/components/parameters/SinceFilter' + - in: query + name: user + description: An ID or username of a user to filter checks by. + schema: + title: '' + type: string + - in: query + name: state + description: A check state to filter checks by. + schema: + title: '' + type: string + enum: + - pending + - success + - warning + - fail + - in: query + name: context + description: A check context to filter checks by. + schema: + title: '' + type: string + responses: + '200': + description: 'List of checks' + headers: + Link: + $ref: '#/components/headers/Link' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Check' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - checks + post: + summary: Create a check. + operationId: checks_create + security: + - basicAuth: [] + - apiKeyAuth: [] + requestBody: + $ref: '#/components/requestBodies/Check' + responses: + '201': + description: 'Created check' + content: + application/json: + schema: + $ref: '#/components/schemas/Check' + '400': + description: 'Invalid request' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorCheckCreate' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - checks + /api/1.4/patches/{patch_id}/checks/{check_id}: + parameters: + - in: path + name: patch_id + description: A unique integer value identifying the parent patch. + required: true + schema: + title: Patch ID + type: integer + - in: path + name: check_id + description: A unique integer value identifying this check. + required: true + schema: + title: Check ID + type: integer + get: + summary: Show a check. + description: | + Retrieve a check by its ID. + operationId: checks_read + responses: + '200': + description: 'A check' + content: + application/json: + schema: + $ref: '#/components/schemas/Check' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - checks + /api/1.4/people: + get: + summary: List people. + description: | + List all people. + A person is anyone that has submitted a patch, a series of patches, or a comment to any project. + operationId: people_list + security: + - basicAuth: [] + - apiKeyAuth: [] + parameters: + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PageSize' + - $ref: '#/components/parameters/Order' + - $ref: '#/components/parameters/Search' + responses: + '200': + description: 'List of people' + headers: + Link: + $ref: '#/components/headers/Link' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Person' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - people + /api/1.4/people/{id}: + parameters: + - in: path + name: id + description: A unique integer value identifying this person. + required: true + schema: + title: ID + type: integer + get: + summary: Show a person. + description: | + Retrieve a person by their ID. + A person is anyone that has submitted a patch, a series of patches, or a comment to any project. + operationId: people_read + security: + - basicAuth: [] + - apiKeyAuth: [] + responses: + '200': + description: 'A person' + content: + application/json: + schema: + $ref: '#/components/schemas/Person' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - people + /api/1.4/projects: + get: + summary: List projects. + description: | + List all projects. + operationId: projects_list + parameters: + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PageSize' + - $ref: '#/components/parameters/Order' + - $ref: '#/components/parameters/Search' + responses: + '200': + description: 'List of projects' + headers: + Link: + $ref: '#/components/headers/Link' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Project' + tags: + - projects + /api/1.4/projects/{id}: + parameters: + - in: path + name: id + description: A unique integer value identifying this project. + required: true + schema: + title: ID + # TODO: Add regex? + type: string + get: + summary: Show a project. + description: | + Retrieve a project by its ID. + operationId: projects_read + responses: + '200': + description: 'A project' + content: + application/json: + schema: + $ref: '#/components/schemas/Project' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - projects + patch: + summary: Update a project (partial). + description: + Partially update an existing project. + You must be a maintainer of the project. + operationId: projects_partial_update + security: + - basicAuth: [] + - apiKeyAuth: [] + requestBody: + $ref: '#/components/requestBodies/Project' + responses: + '200': + description: 'Updated project' + content: + application/json: + schema: + $ref: '#/components/schemas/Project' + '400': + description: 'Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorProjectUpdate' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - projects + put: + description: Update a project. + operationId: projects_update + security: + - basicAuth: [] + - apiKeyAuth: [] + requestBody: + $ref: '#/components/requestBodies/Project' + responses: + '200': + description: 'Updated project' + content: + application/json: + schema: + $ref: '#/components/schemas/Project' + '400': + description: 'Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorProjectUpdate' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - projects + /api/1.4/series: + get: + summary: List series. + description: | + List all series. + A series is a collection of patches with an optional cover letter. + operationId: series_list + parameters: + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PageSize' + - $ref: '#/components/parameters/Order' + - $ref: '#/components/parameters/Search' + - $ref: '#/components/parameters/BeforeFilter' + - $ref: '#/components/parameters/SinceFilter' + - in: query + name: submitter + description: An ID or email address of a person to filter series by. + schema: + title: '' + type: string + - in: query + name: project + description: An ID or linkname of a project to filter series by. + schema: + title: '' + type: string + responses: + '200': + description: 'List of series' + headers: + Link: + $ref: '#/components/headers/Link' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Series' + tags: + - series + /api/1.4/series/{id}: + parameters: + - in: path + name: id + description: A unique integer value identifying this series. + required: true + schema: + title: ID + type: integer + get: + summary: Show a series. + description: | + Retrieve a series by its ID. + A series is a collection of patches with an optional cover letter. + operationId: series_read + responses: + '200': + description: 'A series' + content: + application/json: + schema: + $ref: '#/components/schemas/Series' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - series + patch: + summary: Link both series. + description: | + Apply a partial update to a Series + operationId: series_link + responses: + '200': + description: 'Updated series' + content: + application/json: + schema: + $ref: '#/components/schemas/Series' + '400': + description: 'Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - series + /api/1.4/users: + get: + summary: List users. + description: | + List all users. + operationId: users_list + security: + - basicAuth: [] + - apiKeyAuth: [] + parameters: + - $ref: '#/components/parameters/Page' + - $ref: '#/components/parameters/PageSize' + - $ref: '#/components/parameters/Order' + - $ref: '#/components/parameters/Search' + responses: + '200': + description: 'List of users' + headers: + Link: + $ref: '#/components/headers/Link' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - users + /api/1.4/users/{id}: + parameters: + - in: path + name: id + description: A unique integer value identifying this user. + required: true + schema: + title: ID + type: integer + get: + summary: Show a user. + description: | + Retrieve a user by their ID. + operationId: users_read + security: + - basicAuth: [] + - apiKeyAuth: [] + responses: + '200': + description: 'A user' + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetail' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - users + patch: + summary: Update a user (partial). + description: + Partially update a user account. + Only super users are allowed to update other user's accounts. + operationId: users_partial_update + security: + - basicAuth: [] + - apiKeyAuth: [] + requestBody: + $ref: '#/components/requestBodies/User' + responses: + '200': + description: 'Updated user' + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetail' + '400': + description: 'Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorUserUpdate' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - users + put: + description: Update a user. + operationId: users_update + security: + - basicAuth: [] + - apiKeyAuth: [] + requestBody: + $ref: '#/components/requestBodies/User' + responses: + '200': + description: 'Updated user' + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetail' + '400': + description: 'Bad request' + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorUserUpdate' + '403': + description: 'Forbidden' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 'Not found' + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + tags: + - users +components: + securitySchemes: + basicAuth: + type: http + scheme: basic + description: | + Basic authentication. This should be avoided and may be removed in a future API release. + apiKeyAuth: + type: http + scheme: token + description: | + Token-based authentication. + cookieAuth: + type: apiKey + in: cookie + name: JSESSIONID + description: | + Cookie-based authentication. This is mainly used for the browsable API. + parameters: + Page: + in: query + name: page + description: A page number within the paginated result set. + schema: + title: Page + type: integer + PageSize: + in: query + name: per_page + description: Number of results to return per page. + schema: + title: Page size + type: integer + Order: + in: query + name: order + description: Which field to use when ordering the results. + schema: + title: Ordering + type: string + Search: + in: query + name: q + description: A search term. + schema: + title: Search + type: string + BeforeFilter: + in: query + name: before + description: Latest date-time to retrieve results for. + schema: + title: '' + type: string + SinceFilter: + in: query + name: since + description: Earliest date-time to retrieve results for. + schema: + title: '' + type: string + headers: + Link: + description: | + Links to related resources, in the format defined by + [RFC 5988](https://tools.ietf.org/html/rfc5988#section-5). + This will include a link with relation type `next` to the + next page and `prev` to the previous page, if there is a next + or previous page. It will also include links with the + relation type `first` and `last` pointing to the first and + last page, respectively. + schema: + type: string + requestBodies: + Bundle: + required: true + description: | + A patch bundle. + content: + application/json: + schema: + $ref: '#/components/schemas/BundleCreateUpdate' + multipart/form-data: + schema: + $ref: '#/components/schemas/BundleCreateUpdate' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/BundleCreateUpdate' + Check: + required: true + description: | + A patch check. + content: + application/json: + schema: + $ref: '#/components/schemas/CheckCreate' + multipart/form-data: + schema: + $ref: '#/components/schemas/CheckCreate' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/CheckCreate' + Comment: + required: true + description: | + A patch or cover letter comment. + content: + application/json: + schema: + $ref: '#/components/schemas/CommentUpdate' + Patch: + required: true + description: | + A patch. + content: + application/json: + schema: + $ref: '#/components/schemas/PatchUpdate' + multipart/form-data: + schema: + $ref: '#/components/schemas/PatchUpdate' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/PatchUpdate' + Project: + required: true + description: | + A project. + content: + application/json: + schema: + $ref: '#/components/schemas/Project' + multipart/form-data: + schema: + $ref: '#/components/schemas/Project' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Project' + User: + required: true + description: | + A user. + content: + application/json: + schema: + $ref: '#/components/schemas/UserDetail' + multipart/form-data: + schema: + $ref: '#/components/schemas/UserDetail' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/UserDetail' + schemas: + Index: + type: object + name: Index + description: | + Paths to resource APIs + properties: + bundles: + title: Bundles URL + type: string + format: uri + readOnly: true + covers: + title: Covers URL + type: string + format: uri + readOnly: true + events: + title: Events URL + type: string + format: uri + readOnly: true + patches: + title: Patches URL + type: string + format: uri + readOnly: true + people: + title: People URL + type: string + format: uri + readOnly: true + projects: + title: Projects URL + type: string + format: uri + readOnly: true + users: + title: Users URL + type: string + format: uri + readOnly: true + series: + title: Series URL + type: string + format: uri + readOnly: true + Bundle: + required: + - name + type: object + title: Bundle + description: | + A patch bundle + properties: + id: + title: ID + type: integer + readOnly: true + url: + title: URL + type: string + format: uri + readOnly: true + web_url: + title: Web URL + type: string + format: uri + readOnly: true + project: + $ref: '#/components/schemas/ProjectEmbedded' + name: + title: Name + type: string + minLength: 1 + maxLength: 50 + owner: + title: Owner + readOnly: true + type: + - 'null' + - 'object' + oneOf: + - type: 'null' + - $ref: '#/components/schemas/UserEmbedded' + patches: + title: Patches + type: array + items: + $ref: '#/components/schemas/PatchEmbedded' + uniqueItems: true + public: + title: Public + type: boolean + mbox: + title: Mbox + description: | + A URL to download the bundle in mbox format. Patches will be + ordered in the same order that they are defined in the bundle. + type: string + format: uri + readOnly: true + BundleCreateUpdate: + type: object + title: Bundle create or update + description: | + The fields to set on a new or existing bundle. + required: + - name + properties: + name: + title: Name + type: string + minLength: 1 + maxLength: 50 + patches: + title: Patches + type: array + items: + type: integer + uniqueItems: true + public: + title: Public + type: boolean + Check: + type: object + title: Check + description: | + A patch check + properties: + id: + title: ID + type: integer + readOnly: true + url: + title: Url + type: string + format: uri + readOnly: true + user: + $ref: '#/components/schemas/UserEmbedded' + date: + title: Date + type: string + format: iso8601 + readOnly: true + state: + title: State + description: The state of the check. + type: string + enum: + - pending + - success + - warning + - fail + target_url: + title: Target URL + description: | + The target URL to associate with this check. This should be + specific to the patch. + type: + - 'null' + - 'string' + oneOf: + - type: 'null' + - type: string + format: uri + maxLength: 200 + context: + title: Context + description: | + A label to discern check from checks of other testing systems. + type: string + pattern: ^[-a-zA-Z0-9_]+$ + minLength: 1 + maxLength: 255 + description: + title: Description + description: A brief description of the check. + type: + - 'null' + - 'string' + CheckCreate: + type: object + title: Check + description: | + A patch check + required: + - state + properties: + state: + title: State + description: The state of the check. + type: string + enum: + - pending + - success + - warning + - fail + target_url: + title: Target URL + description: | + The target URL to associate with this check. This should be + specific to the patch. + type: + - 'null' + - 'string' + oneOf: + - type: 'null' + - type: string + format: uri + maxLength: 200 + context: + title: Context + description: | + A label to discern check from checks of other testing systems. + type: string + pattern: ^[-a-zA-Z0-9_]+$ + minLength: 1 + maxLength: 255 + description: + title: Description + description: A brief description of the check. + type: + - 'null' + - 'string' + Comment: + type: object + title: Comment + description: | + A comment + properties: + id: + title: ID + type: integer + readOnly: true + url: + title: URL + type: string + format: uri + readOnly: true + web_url: + title: Web URL + type: string + format: uri + readOnly: true + msgid: + title: Message ID + type: string + readOnly: true + minLength: 1 + maxLength: 255 + list_archive_url: + title: List archive URL + readOnly: true + type: + - 'null' + - 'string' + oneOf: + - type: 'null' + - type: string + format: uri + maxLength: 2000 + date: + title: Date + type: string + format: iso8601 + readOnly: true + subject: + title: Subject + type: string + readOnly: true + submitter: + type: object + title: Submitter + readOnly: true + allOf: + - $ref: '#/components/schemas/PersonEmbedded' + content: + title: Content + type: string + readOnly: true + minLength: 1 + headers: + title: Headers + anyOf: + - type: object + additionalProperties: + type: array + items: + type: string + - type: object + additionalProperties: + type: string + readOnly: true + addressed: + title: Addressed + type: + - 'null' + - 'boolean' + CommentUpdate: + type: object + title: Comment update + description: | + The fields to set on an existing comment. + properties: + addressed: + title: Addressed + type: + - 'null' + - 'boolean' + CoverList: + type: object + title: Cover letters + description: | + A list of cover letters + properties: + id: + title: ID + type: integer + readOnly: true + url: + title: URL + type: string + format: uri + readOnly: true + web_url: + title: Web URL + type: string + format: uri + readOnly: true + project: + $ref: '#/components/schemas/ProjectEmbedded' + msgid: + title: Message ID + type: string + readOnly: true + minLength: 1 + maxLength: 255 + list_archive_url: + title: List archive URL + readOnly: true + type: + - 'null' + - 'string' + oneOf: + - type: 'null' + - type: string + format: uri + maxLength: 2000 + date: + title: Date + type: string + format: iso8601 + readOnly: true + name: + title: Name + type: string + readOnly: true + minLength: 1 + maxLength: 255 + submitter: + type: object + title: Submitter + readOnly: true + allOf: + - $ref: '#/components/schemas/PersonEmbedded' + mbox: + title: Mbox + description: | + A URL to download the cover letter in mbox format. + type: string + format: uri + readOnly: true + series: + type: array + items: + $ref: '#/components/schemas/SeriesEmbedded' + readOnly: true + comments: + title: Comments + type: string + format: uri + readOnly: true + CoverDetail: + type: object + title: Cover letters + description: | + A list of cover letters + allOf: + - $ref: '#/components/schemas/CoverList' + - type: object + properties: + headers: + title: Headers + anyOf: + - type: object + additionalProperties: + type: array + items: + type: string + - type: object + additionalProperties: + type: string + readOnly: true + content: + title: Content + type: string + readOnly: true + minLength: 1 + EventBase: + type: object + title: Event base + description: | + Base event. Not directly used. + properties: + id: + title: ID + type: integer + readOnly: true + category: + title: Category + description: The category of the event. + type: string + readOnly: true + project: + $ref: '#/components/schemas/ProjectEmbedded' + date: + title: Date + description: The time this event was created. + type: string + format: iso8601 + readOnly: true + actor: + title: Actor + description: The user that caused/created this event. + readOnly: true + type: + - 'null' + - 'object' + oneOf: + - type: 'null' + - $ref: '#/components/schemas/UserEmbedded' + payload: + type: object + EventCoverCreated: + title: Cover created event + description: | + A cover created event. + allOf: + - $ref: '#/components/schemas/EventBase' + - type: object + properties: + category: + enum: + - cover-created + payload: + properties: + cover: + $ref: '#/components/schemas/CoverEmbedded' + EventPatchCreated: + title: Patch created event + description: | + A patch created event. + allOf: + - $ref: '#/components/schemas/EventBase' + - type: object + properties: + category: + enum: + - patch-created + payload: + properties: + patch: + $ref: '#/components/schemas/PatchEmbedded' + EventPatchCompleted: + title: Patch completed event + description: | + A patch completed event. + allOf: + - $ref: '#/components/schemas/EventBase' + - type: object + properties: + category: + enum: + - patch-completed + payload: + properties: + patch: + $ref: '#/components/schemas/PatchEmbedded' + series: + $ref: '#/components/schemas/SeriesEmbedded' + EventPatchStateChanged: + title: Patch state change event + description: | + A patch state changed event. + allOf: + - $ref: '#/components/schemas/EventBase' + - type: object + properties: + category: + enum: + - patch-state-changed + payload: + properties: + patch: + $ref: '#/components/schemas/PatchEmbedded' + previous_state: + title: Previous state + type: string + current_state: + title: Current state + type: string + EventPatchRelationChanged: + title: Patch relation change event + description: | + A patch relation changed event. + allOf: + - $ref: '#/components/schemas/EventBase' + - type: object + properties: + category: + enum: + - patch-relation-changed + payload: + properties: + patch: + $ref: '#/components/schemas/PatchEmbedded' + previous_relation: + title: Previous relation + type: + - 'null' + - 'string' + current_relation: + title: Current relation + type: + - 'null' + - 'string' + EventPatchDelegated: + title: Patch delegated event + description: | + A patch delegated event. + allOf: + - $ref: '#/components/schemas/EventBase' + - type: object + properties: + category: + enum: + - patch-delegated + payload: + properties: + patch: + $ref: '#/components/schemas/PatchEmbedded' + previous_delegate: + title: Previous delegate + type: + - 'null' + - 'object' + oneOf: + - type: 'null' + - $ref: '#/components/schemas/UserEmbedded' + current_delegate: + title: Current delegate + type: + - 'null' + - 'object' + oneOf: + - type: 'null' + - $ref: '#/components/schemas/UserEmbedded' + EventCheckCreated: + title: Check create event + description: | + A check created event. + allOf: + - $ref: '#/components/schemas/EventBase' + - type: object + properties: + category: + enum: + - check-created + payload: + properties: + patch: + $ref: '#/components/schemas/PatchEmbedded' + check: + $ref: '#/components/schemas/CheckEmbedded' + EventSeriesCreated: + title: Series create event + description: | + A series created event. + allOf: + - $ref: '#/components/schemas/EventBase' + - type: object + properties: + category: + enum: + - series-created + payload: + properties: + series: + $ref: '#/components/schemas/SeriesEmbedded' + EventSeriesCompleted: + title: Series completed event + description: | + A series completed event. + allOf: + - $ref: '#/components/schemas/EventBase' + - type: object + properties: + category: + enum: + - series-completed + payload: + properties: + series: + $ref: '#/components/schemas/SeriesEmbedded' + EventCoverCommentCreated: + title: Cover letter comment create event + description: | + A comment letter comment created event. + allOf: + - $ref: '#/components/schemas/EventBase' + - type: object + properties: + category: + enum: + - cover-comment-created + payload: + properties: + cover: + $ref: '#/components/schemas/CoverEmbedded' + comment: + $ref: '#/components/schemas/CommentEmbedded' + EventPatchCommentCreated: + title: Patch comment create event + description: | + A patch comment created event. + allOf: + - $ref: '#/components/schemas/EventBase' + - type: object + properties: + category: + enum: + - patch-comment-created + payload: + properties: + patch: + $ref: '#/components/schemas/PatchEmbedded' + comment: + $ref: '#/components/schemas/CommentEmbedded' + PatchList: + required: + - state + - delegate + type: object + title: Patches + description: | + A list of patches. + properties: + id: + title: ID + type: integer + readOnly: true + url: + title: URL + type: string + format: uri + readOnly: true + web_url: + title: Web URL + type: string + format: uri + readOnly: true + project: + $ref: '#/components/schemas/ProjectEmbedded' + msgid: + title: Message ID + type: string + readOnly: true + minLength: 1 + maxLength: 255 + list_archive_url: + title: List archive URL + readOnly: true + type: + - 'null' + - 'string' + oneOf: + - type: 'null' + - type: string + format: uri + maxLength: 2000 + date: + title: Date + type: string + format: iso8601 + readOnly: true + name: + title: Name + type: string + readOnly: true + minLength: 1 + maxLength: 255 + commit_ref: + title: Commit ref + type: + - 'null' + - 'string' + oneOf: + - type: 'null' + - type: string + maxLength: 255 + pull_url: + title: Pull URL + type: + - 'null' + - 'string' + oneOf: + - type: 'null' + - type: string + format: uri + maxLength: 255 + state: + title: State + type: string + archived: + title: Archived + type: boolean + hash: + title: Hash + type: string + readOnly: true + minLength: 1 + submitter: + type: object + title: Submitter + readOnly: true + allOf: + - $ref: '#/components/schemas/PersonEmbedded' + delegate: + title: Delegate + readOnly: true + type: + - 'null' + - 'object' + oneOf: + - type: 'null' + - $ref: '#/components/schemas/UserEmbedded' + mbox: + title: Mbox + description: | + A URL to download the patch in mbox format. Add the `series=*` + querystring parameter to include series dependencies in the mbox + file. + type: string + format: uri + readOnly: true + series: + type: array + items: + $ref: '#/components/schemas/SeriesEmbedded' + readOnly: true + comments: + title: Comments + type: string + format: uri + readOnly: true + check: + title: Check + type: string + readOnly: true + enum: + - pending + - success + - warning + - fail + checks: + title: Checks + type: string + format: uri + readOnly: true + tags: + title: Tags + type: object + additionalProperties: + type: string + readOnly: true + related: + title: Relations + type: array + items: + $ref: '#/components/schemas/PatchEmbedded' + PatchDetail: + type: object + title: Patches + description: | + A list of patches. + allOf: + - $ref: '#/components/schemas/PatchList' + - type: object + properties: + headers: + title: Headers + anyOf: + - type: object + additionalProperties: + type: array + items: + type: string + - type: object + additionalProperties: + type: string + readOnly: true + content: + title: Content + type: string + readOnly: true + minLength: 1 + diff: + title: Diff + type: string + readOnly: true + minLength: 1 + prefixes: + title: Prefixes + type: array + items: + type: string + readOnly: true + PatchUpdate: + type: object + title: Patch update + description: | + The fields to set on an existing patch. + properties: + commit_ref: + title: Commit ref + type: + - 'null' + - 'string' + oneOf: + - type: 'null' + - type: string + maxLength: 255 + pull_url: + title: Pull URL + type: + - 'null' + - 'string' + oneOf: + - type: 'null' + - type: string + format: uri + maxLength: 255 + state: + title: State + type: string + archived: + title: Archived + type: boolean + delegate: + title: Delegate + type: + - 'null' + - 'integer' + related: + title: Relations + type: array + items: + type: integer + Person: + type: object + title: Person + description: | + A person + properties: + id: + title: ID + type: integer + readOnly: true + url: + title: URL + type: string + format: uri + readOnly: true + name: + title: Name + type: string + readOnly: true + minLength: 1 + maxLength: 255 + email: + title: Email + type: string + format: email + readOnly: true + minLength: 1 + maxLength: 255 + user: + title: User + readOnly: true + type: + - 'null' + - 'object' + oneOf: + - type: 'null' + - $ref: '#/components/schemas/UserEmbedded' + Project: + type: object + title: Project + description: | + A project. + properties: + id: + title: ID + type: integer + readOnly: true + url: + title: URL + type: string + format: uri + readOnly: true + name: + title: Name + type: string + readOnly: true + minLength: 1 + maxLength: 255 + link_name: + title: Link name + type: string + readOnly: true + minLength: 1 + maxLength: 255 + list_id: + title: List ID + type: string + readOnly: true + minLength: 1 + maxLength: 255 + list_email: + title: List email + type: string + format: email + readOnly: true + minLength: 1 + maxLength: 200 + web_url: + title: Web URL + type: string + format: uri + maxLength: 2000 + scm_url: + title: SCM URL + type: string + format: uri + maxLength: 2000 + webscm_url: + title: Web SCM URL + type: string + format: uri + maxLength: 2000 + maintainers: + type: array + items: + $ref: '#/components/schemas/UserEmbedded' + readOnly: true + uniqueItems: true + subject_match: + title: Subject match + description: | + Regex to match the subject against if only part of emails sent to + the list belongs to this project. Will be used with IGNORECASE and + MULTILINE flags. If rules for more projects match the first one + returned from DB is chosen; empty field serves as a default for + every email which has no other match. + type: string + readOnly: true + maxLength: 64 + list_archive_url: + title: List archive URL + type: + - 'null' + - 'string' + oneOf: + - type: 'null' + - type: string + format: uri + maxLength: 2000 + list_archive_url_format: + title: List archive URL format + description: | + URL format for the list archive's Message-ID redirector. {} will be + replaced by the Message-ID. + type: + - 'null' + - 'string' + oneOf: + - type: 'null' + - type: string + format: uri + maxLength: 2000 + commit_url_format: + title: Web SCM URL format for a particular commit + type: string + Series: + type: object + title: Series + description: | + A series + properties: + id: + title: ID + type: integer + readOnly: true + url: + title: URL + type: string + format: uri + readOnly: true + web_url: + title: Web URL + type: string + format: uri + readOnly: true + project: + $ref: '#/components/schemas/ProjectEmbedded' + name: + title: Name + description: | + An optional name to associate with the series, e.g. "John's PCI + series". + type: + - 'null' + - 'string' + oneOf: + - type: 'null' + - type: 'string' + maxLength: 255 + date: + title: Date + type: string + format: iso8601 + readOnly: true + submitter: + type: object + title: Submitter + readOnly: true + allOf: + - $ref: '#/components/schemas/PersonEmbedded' + version: + title: Version + description: | + Version of series as indicated by the subject prefix(es). + type: integer + total: + title: Total + description: | + Number of patches in series as indicated by the subject prefix(es). + type: integer + readOnly: true + received_total: + title: Received total + type: integer + readOnly: true + received_all: + title: Received all + type: boolean + readOnly: true + mbox: + title: Mbox + description: | + A URL to download the series in mbox format. + type: string + format: uri + readOnly: true + cover_letter: + $ref: '#/components/schemas/CoverEmbedded' + patches: + title: Patches + type: array + items: + $ref: '#/components/schemas/PatchEmbedded' + readOnly: true + uniqueItems: true + related_series: + title: Related series + type: array + items: + $ref: '#/components/schemas/Series' + readOnly: true + uniqueItems: true + User: + type: object + title: User + description: | + A user + properties: + id: + title: ID + type: integer + readOnly: true + url: + title: URL + type: string + format: uri + readOnly: true + username: + title: Username + type: string + readOnly: true + minLength: 1 + maxLength: 150 + first_name: + title: First name + type: string + maxLength: 30 + last_name: + title: Last name + type: string + maxLength: 150 + email: + title: Email address + type: string + format: email + readOnly: true + minLength: 1 + UserDetail: + type: object + title: User + description: | + A user + allOf: + - $ref: '#/components/schemas/User' + - type: object + properties: + settings: + type: object + properties: + send_email: + title: Send email + description: | + Whether Patchwork should send email on your behalf. + Only present and configurable for your account. + type: boolean + items_per_page: + title: Items per page + description: | + Number of items to display per page (web UI). + Only present and configurable for your account. + type: integer + show_ids: + title: Show IDs + description: | + Show click-to-copy IDs in the list view (web UI). + Only present and configurable for your account. + type: boolean + CheckEmbedded: + type: object + title: Check + description: | + A patch check + properties: + id: + title: ID + type: integer + readOnly: true + url: + title: Url + type: string + format: uri + readOnly: true + date: + title: Date + type: string + format: iso8601 + readOnly: true + state: + title: State + description: The state of the check. + type: string + readOnly: true + enum: + - pending + - success + - warning + - fail + target_url: + title: Target url + description: | + The target URL to associate with this check. This should be specific + to the patch. + readOnly: true + type: + - 'null' + - 'string' + oneOf: + - type: 'null' + - type: string + format: uri + maxLength: 200 + context: + title: Context + description: | + A label to discern check from checks of other testing systems. + type: string + pattern: ^[-a-zA-Z0-9_]+$ + maxLength: 255 + minLength: 1 + readOnly: true + CommentEmbedded: + type: object + title: Comment + description: | + A comment + properties: + id: + title: ID + type: integer + readOnly: true + url: + title: URL + type: string + format: uri + readOnly: true + web_url: + title: Web URL + type: string + format: uri + readOnly: true + msgid: + title: Message ID + type: string + readOnly: true + minLength: 1 + list_archive_url: + title: List archive URL + readOnly: true + type: + - 'null' + - 'string' + date: + title: Date + type: string + format: iso8601 + readOnly: true + name: + title: Name + type: string + readOnly: true + minLength: 1 + CoverEmbedded: + type: object + title: Cover letter + description: | + A cover letter + properties: + id: + title: ID + type: integer + readOnly: true + url: + title: URL + type: string + format: uri + readOnly: true + web_url: + title: Web URL + type: string + format: uri + readOnly: true + msgid: + title: Message ID + type: string + readOnly: true + minLength: 1 + list_archive_url: + title: List archive URL + readOnly: true + type: + - 'null' + - 'string' + date: + title: Date + type: string + format: iso8601 + readOnly: true + name: + title: Name + type: string + readOnly: true + minLength: 1 + mbox: + title: Mbox + description: | + A URL to download the cover letter in mbox format. + type: string + format: uri + readOnly: true + PatchEmbedded: + type: object + title: Patch + description: | + A patch + properties: + id: + title: ID + type: integer + readOnly: true + url: + title: URL + type: string + format: uri + readOnly: true + web_url: + title: Web URL + type: string + format: uri + readOnly: true + msgid: + title: Message ID + type: string + readOnly: true + minLength: 1 + list_archive_url: + title: List archive URL + readOnly: true + type: + - 'null' + - 'string' + date: + title: Date + type: string + format: iso8601 + readOnly: true + name: + title: Name + type: string + readOnly: true + minLength: 1 + mbox: + title: Mbox + description: | + A URL to download the patch in mbox format. Add the `series=*` + querystring parameter to include series dependencies in the mbox + file. + type: string + format: uri + readOnly: true + PersonEmbedded: + type: object + title: Person + description: | + A person + properties: + id: + title: ID + type: integer + readOnly: true + url: + title: URL + type: string + format: uri + readOnly: true + name: + title: Name + type: string + readOnly: true + minLength: 1 + email: + title: Email + type: string + format: email + readOnly: true + minLength: 1 + ProjectEmbedded: + type: object + title: Project + description: | + A project + properties: + id: + title: ID + type: integer + readOnly: true + url: + title: URL + type: string + format: uri + readOnly: true + name: + title: Name + type: string + readOnly: true + minLength: 1 + link_name: + title: Link name + type: string + readOnly: true + maxLength: 255 + minLength: 1 + list_id: + title: List ID + type: string + readOnly: true + maxLength: 255 + minLength: 1 + list_email: + title: List email + type: string + format: email + readOnly: true + maxLength: 200 + minLength: 1 + web_url: + title: Web URL + type: string + format: uri + readOnly: true + maxLength: 2000 + scm_url: + title: SCM URL + type: string + format: uri + readOnly: true + maxLength: 2000 + webscm_url: + title: WebSCM URL + type: string + format: uri + readOnly: true + maxLength: 2000 + list_archive_url: + title: List archive URL + type: + - 'null' + - 'string' + oneOf: + - type: 'null' + - type: string + format: uri + maxLength: 2000 + list_archive_url_format: + title: List archive URL format + description: | + URL format for the list archive's Message-ID redirector. {} will be + replaced by the Message-ID. + type: + - 'null' + - 'string' + oneOf: + - type: 'null' + - type: string + format: uri + maxLength: 2000 + commit_url_format: + title: Web SCM URL format for a particular commit + type: string + readOnly: true + SeriesEmbedded: + type: object + title: Series + description: | + A series + properties: + id: + title: ID + type: integer + readOnly: true + url: + title: URL + type: string + format: uri + readOnly: true + web_url: + title: Web URL + type: string + format: uri + readOnly: true + name: + title: Name + description: | + An optional name to associate with the series, e.g. "John's PCI + series". + readOnly: true + type: + - 'null' + - 'string' + oneOf: + - type: 'null' + - type: string + maxLength: 255 + date: + title: Date + type: string + format: iso8601 + readOnly: true + version: + title: Version + description: | + Version of series as indicated by the subject prefix(es). + type: integer + readOnly: true + mbox: + title: Mbox + description: | + A URL to download the series in mbox format. + type: string + format: uri + readOnly: true + UserEmbedded: + type: object + title: User + description: | + A user + properties: + id: + title: ID + type: integer + readOnly: true + url: + title: URL + type: string + format: uri + readOnly: true + username: + title: Username + type: string + readOnly: true + minLength: 1 + maxLength: 150 + first_name: + title: First name + type: string + maxLength: 30 + readOnly: true + last_name: + title: Last name + type: string + maxLength: 150 + readOnly: true + email: + title: Email address + type: string + format: email + readOnly: true + minLength: 1 + Error: + type: object + title: A generic error. + description: | + A generic error. + properties: + detail: + title: Detail + type: string + readOnly: true + ErrorBundleCreateUpdate: + type: object + title: A bundle creation or update error. + description: | + A mapping of field names to validation failures. + properties: + name: + title: Name + type: array + items: + type: string + readOnly: true + patches: + title: Patches + type: array + items: + type: string + readOnly: true + public: + title: Public + type: array + items: + type: string + ErrorCheckCreate: + type: object + title: A check creation error. + description: | + A mapping of field names to validation failures. + properties: + state: + title: State + type: array + items: + type: string + readOnly: true + target_url: + title: Target URL + type: array + items: + type: string + readOnly: true + context: + title: Context + type: array + items: + type: string + readOnly: true + description: + title: Description + type: array + items: + type: string + readOnly: true + ErrorCommentUpdate: + type: object + title: A comment update error. + description: | + A mapping of field names to validation failures. + properties: + addressed: + title: Addressed + type: array + items: + type: string + ErrorPatchUpdate: + type: object + title: A patch update error. + description: | + A mapping of field names to validation failures. + properties: + state: + title: State + type: array + items: + type: string + readOnly: true + delegate: + title: Delegate + type: array + items: + type: string + readOnly: true + commit_ref: + title: Commit ref + type: array + items: + type: string + readOnly: true + archived: + title: Archived + type: array + items: + type: string + readOnly: true + ErrorProjectUpdate: + type: object + title: A project update error. + description: | + A mapping of field names to validation failures. + properties: + web_url: + title: Web URL + type: string + format: uri + readOnly: true + scm_url: + title: SCM URL + type: string + format: uri + readOnly: true + webscm_url: + title: Web SCM URL + type: string + format: uri + readOnly: true + ErrorUserUpdate: + type: object + title: A user update error. + description: | + A mapping of field names to validation failures. + properties: + first_name: + title: First name + type: string + readOnly: true + last_name: + title: First name + type: string + readOnly: true +tags: + - name: api + description: General API operations + - name: patches + description: Patch operations + - name: covers + description: Cover letter operations + - name: series + description: Series operations + - name: comments + description: Comment operations + - name: people + description: Submitter operations + - name: users + description: User operations + - name: bundles + description: Bundle operations + - name: projects + description: Project operations + - name: bundles + description: Bundle operations + - name: checks + description: Check operations + - name: events + description: Event operations From 2ea356c38cf86237efe9a7b58a82bad80f3be6a4 Mon Sep 17 00:00:00 2001 From: andrepapoti Date: Mon, 18 Mar 2024 11:41:59 -0300 Subject: [PATCH 45/46] tests: Add tests for linking series feature Closes #506 Signed-off-by: andrepapoti --- patchwork/tests/api/test_series.py | 74 ++++++++++++++++++++++++++++++ patchwork/tests/test_series.py | 48 +++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/patchwork/tests/api/test_series.py b/patchwork/tests/api/test_series.py index 597b61a94..801e94492 100644 --- a/patchwork/tests/api/test_series.py +++ b/patchwork/tests/api/test_series.py @@ -2,6 +2,7 @@ # Copyright (C) 2018 Stephen Finucane # # SPDX-License-Identifier: GPL-2.0-or-later +import json from django.test import override_settings from django.urls import NoReverseMatch @@ -16,6 +17,7 @@ from patchwork.tests.utils import create_project from patchwork.tests.utils import create_series from patchwork.tests.utils import create_user +from patchwork.models import Person @override_settings(ENABLE_REST_API=True) @@ -203,3 +205,75 @@ def test_create_update_delete(self): resp = self.client.delete(self.api_url(series.id)) self.assertEqual(status.HTTP_405_METHOD_NOT_ALLOWED, resp.status_code) + + def _generate_related_ids_payload(self, series, id): + related_ids = series.related_series.all().values_list('id', flat=True) + return list(related_ids) + [id] + + def test_series_linking(self): + user = create_user() + person = Person.objects.get(user=user) + project_obj = create_project(linkname='myproject') + series_a = create_series(project=project_obj, submitter=person) + create_cover(series=series_a) + create_patch(series=series_a) + + self.client.authenticate(user=user) + url = reverse('api-series-detail', kwargs={'pk': series_a.id}) + + # Link to another series + series_b = create_series( + project=series_a.project, submitter=series_a.submitter + ) + + resp = self.client.patch( + url, + data={ + 'related_series': self._generate_related_ids_payload( + series_a, series_b.id + ) + }, + ) + related_series = json.loads(resp.content).get('related_series') + self.assertEqual(resp.status_code, status.HTTP_200_OK) + self.assertEqual(len(related_series), 1) + self.assertEqual( + related_series[0]['web_url'], + f'http://example.com/project/myproject/list/?series={series_b.id}', + ) + + # Link to more than one series + series_c = create_series( + project=series_a.project, submitter=series_a.submitter + ) + resp = self.client.patch( + url, + data={ + 'related_series': self._generate_related_ids_payload( + series_a, series_c.id + ) + }, + ) + + related_series = json.loads(resp.content).get('related_series') + self.assertEqual(resp.status_code, status.HTTP_200_OK) + self.assertEqual(len(related_series), 2) + self.assertEqual( + related_series[1]['web_url'], + f'http://example.com/project/myproject/list/?series={series_c.id}', + ) + + # Link to a series from a different project + series_d = create_series(submitter=series_a.submitter) + + resp = self.client.patch( + url, + data={ + 'related_series': self._generate_related_ids_payload( + series_a, series_d.id + ) + }, + ) + + related_series = json.loads(resp.content).get('related_series') + self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST) diff --git a/patchwork/tests/test_series.py b/patchwork/tests/test_series.py index ce1140427..220cb81ed 100644 --- a/patchwork/tests/test_series.py +++ b/patchwork/tests/test_series.py @@ -8,11 +8,13 @@ import unittest from django.test import TestCase +from rest_framework.request import HttpRequest from patchwork import models from patchwork import parser from patchwork.tests import utils from patchwork.views.utils import patch_to_mbox +from patchwork.api.series import SeriesSerializer TEST_SERIES_DIR = os.path.join(os.path.dirname(__file__), 'series') @@ -804,3 +806,49 @@ def test_custom_name(self): self.assertEqual(series.name, series_name) mbox.close() + + +class SeriesSerializerTestCase(TestCase): + def _mock_request(self): + mock_request = HttpRequest() + mock_request.version = '1.4' + mock_request.META['SERVER_NAME'] = 'example.com' + mock_request.META['SERVER_PORT'] = '8000' + + return mock_request + + def _create_serializer(self, series, related_series, mock_request): + related_ids = list( + series.related_series.all().values_list('id', flat=True) + ) + [related_series.pk] + + return SeriesSerializer( + series, + context={'request': mock_request}, + data={'related_series': related_ids}, + partial=True, + ) + + def test_related_series_validation_equal_project_id(self): + series_a = utils.create_series() + series_b = utils.create_series(project=series_a.project) + + mock_request = self._mock_request() + serializer = self._create_serializer(series_a, series_b, mock_request) + serializer.is_valid() + serializer.save() + + related_series_urls = serializer.data['related_series'] + self.assertEqual(len(related_series_urls), 1) + self.assertEqual( + f'series={series_b.id}' in related_series_urls[0]['web_url'], True + ) + + def test_related_series_validation_different_project_id(self): + series_a = utils.create_series() + series_b = utils.create_series() + + mock_request = self._mock_request() + serializer = self._create_serializer(series_a, series_b, mock_request) + is_valid = serializer.is_valid() + self.assertFalse(is_valid) From a91a6674b1997613e9fd1deaac8343ae280dde4c Mon Sep 17 00:00:00 2001 From: andrepapoti Date: Mon, 18 Mar 2024 15:54:34 -0300 Subject: [PATCH 46/46] release-notes: Add release notes for the Series linking feature Closes #506 Signed-off-by: andrepapoti --- docs/api/rest/index.rst | 49 +++++++++++-------- docs/api/rest/schemas/v1.3.rst | 4 +- docs/api/rest/schemas/v1.4.rst | 5 ++ .../notes/issue-506-ce13fcdc4523a300.yaml | 11 +++++ 4 files changed, 46 insertions(+), 23 deletions(-) create mode 100644 docs/api/rest/schemas/v1.4.rst create mode 100644 releasenotes/notes/issue-506-ce13fcdc4523a300.yaml diff --git a/docs/api/rest/index.rst b/docs/api/rest/index.rst index 67022e6f3..97095d13e 100644 --- a/docs/api/rest/index.rst +++ b/docs/api/rest/index.rst @@ -8,7 +8,7 @@ This guide provides an overview of how one can interact with the REST API. For detailed information on type and response format of the various resources exposed by the API, refer to the web browsable API. This can be found at: - https://patchwork.example.com/api/1.3/ + https://patchwork.example.com/api/1.4/ where `patchwork.example.com` refers to the URL of your Patchwork instance. @@ -43,6 +43,11 @@ If all you want is reference guides, skip straight to :ref:`rest-api-schemas`. The API version was bumped to v1.3 in Patchwork v3.1. The older APIs are still supported. For more information, refer to :ref:`rest-api-versions`. +.. versionchanged:: 3.2 + + The API version was bumped to v1.4 in Patchwork v3.2. The older APIs are + still supported. For more information, refer to :ref:`rest-api-versions`. + Getting Started --------------- @@ -57,16 +62,16 @@ Patchwork instance hosted at `patchwork.example.com`, run: .. code-block:: shell - $ curl -s 'https://patchwork.example.com/api/1.3/' | python -m json.tool + $ curl -s 'https://patchwork.example.com/api/1.4/' | python -m json.tool { - "bundles": "https://patchwork.example.com/api/1.3/bundles/", - "covers": "https://patchwork.example.com/api/1.3/covers/", - "events": "https://patchwork.example.com/api/1.3/events/", - "patches": "https://patchwork.example.com/api/1.3/patches/", - "people": "https://patchwork.example.com/api/1.3/people/", - "projects": "https://patchwork.example.com/api/1.3/projects/", - "series": "https://patchwork.example.com/api/1.3/series/", - "users": "https://patchwork.example.com/api/1.3/users/" + "bundles": "https://patchwork.example.com/api/1.4/bundles/", + "covers": "https://patchwork.example.com/api/1.4/covers/", + "events": "https://patchwork.example.com/api/1.4/events/", + "patches": "https://patchwork.example.com/api/1.4/patches/", + "people": "https://patchwork.example.com/api/1.4/people/", + "projects": "https://patchwork.example.com/api/1.4/projects/", + "series": "https://patchwork.example.com/api/1.4/series/", + "users": "https://patchwork.example.com/api/1.4/users/" } @@ -79,17 +84,17 @@ well-supported. To repeat the above example using `requests`:, run $ python >>> import json >>> import requests - >>> r = requests.get('https://patchwork.example.com/api/1.3/') + >>> r = requests.get('https://patchwork.example.com/api/1.4/') >>> print(json.dumps(r.json(), indent=2)) { - "bundles": "https://patchwork.example.com/api/1.3/bundles/", - "covers": "https://patchwork.example.com/api/1.3/covers/", - "events": "https://patchwork.example.com/api/1.3/events/", - "patches": "https://patchwork.example.com/api/1.3/patches/", - "people": "https://patchwork.example.com/api/1.3/people/", - "projects": "https://patchwork.example.com/api/1.3/projects/", - "series": "https://patchwork.example.com/api/1.3/series/", - "users": "https://patchwork.example.com/api/1.3/users/" + "bundles": "https://patchwork.example.com/api/1.4/bundles/", + "covers": "https://patchwork.example.com/api/1.4/covers/", + "events": "https://patchwork.example.com/api/1.4/events/", + "patches": "https://patchwork.example.com/api/1.4/patches/", + "people": "https://patchwork.example.com/api/1.4/people/", + "projects": "https://patchwork.example.com/api/1.4/projects/", + "series": "https://patchwork.example.com/api/1.4/series/", + "users": "https://patchwork.example.com/api/1.4/users/" } Tools like `curl` and libraries like `requests` can be used to build anything @@ -108,7 +113,7 @@ Versioning ---------- By default, all requests will receive the latest version of the API: currently -``1.3``: +``1.4``: .. code-block:: http @@ -119,7 +124,7 @@ changes breaking your application: .. code-block:: http - GET /api/1.3 HTTP/1.1 + GET /api/1.4 HTTP/1.1 Older API versions will be deprecated and removed over time. For more information, refer to :ref:`rest-api-versions`. @@ -275,6 +280,7 @@ Supported Versions 1.1, 2.1, ✓ 1.2, 2.2, ✓ 1.3, 3.1, ✓ + 1.4, 3.2, ✓ Further information about this and more can typically be found in :doc:`the release notes `. @@ -292,6 +298,7 @@ Auto-generated schema documentation is provided below. /api/rest/schemas/v1.1 /api/rest/schemas/v1.2 /api/rest/schemas/v1.3 + /api/rest/schemas/v1.4 .. Links diff --git a/docs/api/rest/schemas/v1.3.rst b/docs/api/rest/schemas/v1.3.rst index 17a4421ae..6bbf1a560 100644 --- a/docs/api/rest/schemas/v1.3.rst +++ b/docs/api/rest/schemas/v1.3.rst @@ -1,5 +1,5 @@ -API v1.3 (latest) -================= +API v1.3 +======== .. openapi:: ../../schemas/v1.3/patchwork.yaml :examples: diff --git a/docs/api/rest/schemas/v1.4.rst b/docs/api/rest/schemas/v1.4.rst new file mode 100644 index 000000000..11e34f6a5 --- /dev/null +++ b/docs/api/rest/schemas/v1.4.rst @@ -0,0 +1,5 @@ +API v1.4 (latest) +================= + +.. openapi:: ../../schemas/v1.4/patchwork.yaml + :examples: diff --git a/releasenotes/notes/issue-506-ce13fcdc4523a300.yaml b/releasenotes/notes/issue-506-ce13fcdc4523a300.yaml new file mode 100644 index 000000000..ad08e0016 --- /dev/null +++ b/releasenotes/notes/issue-506-ce13fcdc4523a300.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + Added support for Series to link to another Series, allowing for automation to link an older version of a series to a newer one. +api: + - | + The application version has been updated to v3.2. + - | + The API version has been updated to v1.4. + - | + The REST API endpoint ``/api/series/`` now allows PATCH requests.