get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/1911544/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 1911544,
    "url": "http://patchwork.ozlabs.org/api/patches/1911544/?format=api",
    "web_url": "http://patchwork.ozlabs.org/project/patchwork/patch/20240313065642.385843-6-andrepapoti@gmail.com/",
    "project": {
        "id": 16,
        "url": "http://patchwork.ozlabs.org/api/projects/16/?format=api",
        "name": "Patchwork",
        "link_name": "patchwork",
        "list_id": "patchwork.lists.ozlabs.org",
        "list_email": "patchwork@lists.ozlabs.org",
        "web_url": "http://jk.ozlabs.org/projects/patchwork/",
        "scm_url": "git://github.com/getpatchwork/patchwork",
        "webscm_url": "https://github.com/getpatchwork/patchwork",
        "list_archive_url": "",
        "list_archive_url_format": "",
        "commit_url_format": ""
    },
    "msgid": "<20240313065642.385843-6-andrepapoti@gmail.com>",
    "list_archive_url": null,
    "date": "2024-03-13T06:56:38",
    "name": "[6/9] tests: Add tests for Note related endpoints",
    "commit_ref": null,
    "pull_url": null,
    "state": "new",
    "archived": false,
    "hash": "1736df36d7a120e5aca59241ae4418f66e859d67",
    "submitter": {
        "id": 88169,
        "url": "http://patchwork.ozlabs.org/api/people/88169/?format=api",
        "name": "andrepapoti",
        "email": "andrepapoti@gmail.com"
    },
    "delegate": null,
    "mbox": "http://patchwork.ozlabs.org/project/patchwork/patch/20240313065642.385843-6-andrepapoti@gmail.com/mbox/",
    "series": [
        {
            "id": 398839,
            "url": "http://patchwork.ozlabs.org/api/series/398839/?format=api",
            "web_url": "http://patchwork.ozlabs.org/project/patchwork/list/?series=398839",
            "date": "2024-03-13T06:56:33",
            "name": "[1/9] models: Add Note model",
            "version": 1,
            "mbox": "http://patchwork.ozlabs.org/series/398839/mbox/"
        }
    ],
    "comments": "http://patchwork.ozlabs.org/api/patches/1911544/comments/",
    "check": "pending",
    "checks": "http://patchwork.ozlabs.org/api/patches/1911544/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "\n <patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org>",
        "X-Original-To": [
            "incoming@patchwork.ozlabs.org",
            "patchwork@lists.ozlabs.org"
        ],
        "Delivered-To": [
            "patchwork-incoming@legolas.ozlabs.org",
            "patchwork@lists.ozlabs.org"
        ],
        "Authentication-Results": [
            "legolas.ozlabs.org;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256\n header.s=20230601 header.b=BEezmNps;\n\tdkim-atps=neutral",
            "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=lists.ozlabs.org\n (client-ip=2404:9400:2:0:216:3eff:fee1:b9f1; helo=lists.ozlabs.org;\n envelope-from=patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org;\n receiver=patchwork.ozlabs.org)",
            "lists.ozlabs.org;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256\n header.s=20230601 header.b=BEezmNps;\n\tdkim-atps=neutral",
            "lists.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256\n header.s=20230601 header.b=BEezmNps;\n\tdkim-atps=neutral",
            "lists.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=gmail.com\n (client-ip=2607:f8b0:4864:20::42a; helo=mail-pf1-x42a.google.com;\n envelope-from=andrepapoti@gmail.com; receiver=lists.ozlabs.org)"
        ],
        "Received": [
            "from lists.ozlabs.org (lists.ozlabs.org\n [IPv6:2404:9400:2:0:216:3eff:fee1:b9f1])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange X25519 server-signature ECDSA (secp384r1))\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4TvhDK6vDmz1yWn\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 13 Mar 2024 17:58:09 +1100 (AEDT)",
            "from boromir.ozlabs.org (localhost [IPv6:::1])\n\tby lists.ozlabs.org (Postfix) with ESMTP id 4TvhDK5sKrz3vYW\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 13 Mar 2024 17:58:09 +1100 (AEDT)",
            "from mail-pf1-x42a.google.com (mail-pf1-x42a.google.com\n [IPv6:2607:f8b0:4864:20::42a])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest\n SHA256)\n\t(No client certificate requested)\n\tby lists.ozlabs.org (Postfix) with ESMTPS id 4TvhCr1t8xz3fNd\n\tfor <patchwork@lists.ozlabs.org>; Wed, 13 Mar 2024 17:57:44 +1100 (AEDT)",
            "by mail-pf1-x42a.google.com with SMTP id\n d2e1a72fcca58-6e6b729669bso311546b3a.3\n        for <patchwork@lists.ozlabs.org>;\n Tue, 12 Mar 2024 23:57:44 -0700 (PDT)",
            "from localhost.localdomain ([2804:1b3:a4c0:b1de:3ec4:883:9cca:f0a7])\n        by smtp.gmail.com with ESMTPSA id\n le21-20020a056a004fd500b006e691787eaesm4843998pfb.34.2024.03.12.23.57.39\n        (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n        Tue, 12 Mar 2024 23:57:40 -0700 (PDT)"
        ],
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n        d=gmail.com; s=20230601; t=1710313061; x=1710917861;\n darn=lists.ozlabs.org;\n        h=content-transfer-encoding:mime-version:references:in-reply-to\n         :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n         :message-id:reply-to;\n        bh=5BYxYjOFJzBtFqd3jGu5GMLS7R33LgrofCXNJ3DfbKs=;\n        b=BEezmNpsJOKRGeyLZyQP600Azq8l1Km0AyzRGPSRnkSfAc/oxDodg299lYqq/D13qS\n         Hl0Bzm/nx8FHlpSVsTd4RwITf/Tc8q12kQvIze/Jkh2wY7z24A6jXUBpifKjGZUP1RgX\n         WzGqWRDajucFf7p6TkrJrDnu7vybYtFUJvYigid2hz2PxvAJri0hHqeR9NJD5e/3p/Uz\n         EoT+F2TbAoCriQrFpgRnVxkQ/F4QPDhBPRa9BgrMyHWeJultqbpEzrAXOW/kCvtA0ICW\n         mzB8RdyvMcNYTxOnmc1I6MCneYqhQWVKoLDXT9EIE6E8VmR/rCma59CqinDsys96GaHX\n         yWYg==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n        d=1e100.net; s=20230601; t=1710313061; x=1710917861;\n        h=content-transfer-encoding:mime-version:references:in-reply-to\n         :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n         :subject:date:message-id:reply-to;\n        bh=5BYxYjOFJzBtFqd3jGu5GMLS7R33LgrofCXNJ3DfbKs=;\n        b=hF0OIW108va9rCYzhuQ19IN8TtMiuGiCJAOrkDuwupmD0znsSs/pdAv0YfRSVF72rz\n         uTbYKU6FTYA5/pGjgZIgvAEx8mxXM34e/0OVdONgiEdmy6dy5Uc6mWpINHNJy3bAheTN\n         0NystsxLGD+lfgHj9RlCoB14Uoly/JQ1tPFaCZdWdNjUNL1TKHpeS0mIvthhjDYzuejC\n         9Lk5DePEKC7T67DBMHnsQyeJJQSyOXTcrkWoUHpVUIjyFbCyR7Rl1AQWQTDpk+I6Omjk\n         3gfnbXhB6lORT/KwWCnzqzr/VVU8KWFuh6C/d+Gjy8a5yrhlUa1+8Q8wRciadKQi3m6d\n         qTBw==",
        "X-Gm-Message-State": "AOJu0YxobAsFqwiuuwykhin6wBqu1ahF7qebJfZTh7RlxvdTpY/EbgQ9\n\t0jczf0UWKlwFC+qkS6TxJ40LM1QjRDtIrDXa7H1yDZaQY79aSnwjRHUji+pz",
        "X-Google-Smtp-Source": "\n AGHT+IGZWUCSYoLAipxHjpD5NGF5kFm5EqPVomE+gB9jDr2lvN2kxm5ujrhwU6X5T5rDTCL5PIjFOA==",
        "X-Received": "by 2002:a05:6a00:1403:b0:6e6:8964:7fcd with SMTP id\n l3-20020a056a00140300b006e689647fcdmr1757560pfu.24.1710313060996;\n        Tue, 12 Mar 2024 23:57:40 -0700 (PDT)",
        "From": "andrepapoti <andrepapoti@gmail.com>",
        "To": "patchwork@lists.ozlabs.org",
        "Subject": "[PATCH 6/9] tests: Add tests for Note related endpoints",
        "Date": "Wed, 13 Mar 2024 03:56:38 -0300",
        "Message-ID": "<20240313065642.385843-6-andrepapoti@gmail.com>",
        "X-Mailer": "git-send-email 2.44.0",
        "In-Reply-To": "<20240313065642.385843-1-andrepapoti@gmail.com>",
        "References": "<20240313065642.385843-1-andrepapoti@gmail.com>",
        "MIME-Version": "1.0",
        "X-BeenThere": "patchwork@lists.ozlabs.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "Patchwork development <patchwork.lists.ozlabs.org>",
        "List-Unsubscribe": "<https://lists.ozlabs.org/options/patchwork>,\n <mailto:patchwork-request@lists.ozlabs.org?subject=unsubscribe>",
        "List-Archive": "<http://lists.ozlabs.org/pipermail/patchwork/>",
        "List-Post": "<mailto:patchwork@lists.ozlabs.org>",
        "List-Help": "<mailto:patchwork-request@lists.ozlabs.org?subject=help>",
        "List-Subscribe": "<https://lists.ozlabs.org/listinfo/patchwork>,\n <mailto:patchwork-request@lists.ozlabs.org?subject=subscribe>",
        "Content-Type": "text/plain; charset=\"us-ascii\"",
        "Content-Transfer-Encoding": "7bit",
        "Errors-To": "patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org",
        "Sender": "\"Patchwork\"\n <patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org>"
    },
    "content": "Signed-off-by: andrepapoti <andrepapoti@gmail.com>\n---\n patchwork/tests/api/test_notes.py | 232 ++++++++++++++++++++++++++++++\n patchwork/tests/utils.py          |  12 ++\n 2 files changed, 244 insertions(+)\n create mode 100644 patchwork/tests/api/test_notes.py",
    "diff": "diff --git a/patchwork/tests/api/test_notes.py b/patchwork/tests/api/test_notes.py\nnew file mode 100644\nindex 0000000..fc2d2e3\n--- /dev/null\n+++ b/patchwork/tests/api/test_notes.py\n@@ -0,0 +1,232 @@\n+# Patchwork - automated patch tracking system\n+# Copyright (C) 2018 Red Hat\n+#\n+# SPDX-License-Identifier: GPL-2.0-or-later\n+\n+from django.test import override_settings\n+from django.urls import reverse\n+from rest_framework import status\n+\n+from patchwork.models import Note\n+from patchwork.tests.api import utils\n+from patchwork.tests.utils import create_patch\n+from patchwork.tests.utils import create_maintainer\n+from patchwork.tests.utils import create_person\n+from patchwork.tests.utils import create_project\n+from patchwork.tests.utils import create_note\n+from patchwork.tests.utils import create_user\n+\n+\n+@override_settings(ENABLE_REST_API=True)\n+class TestPatchNotes(utils.APITestCase):\n+    def setUp(self):\n+        super(TestPatchNotes, self).setUp()\n+        self.project = create_project()\n+        self.user = create_maintainer(self.project)\n+        self.patch = create_patch(project=self.project)\n+\n+    def check_for_expected(self, instance, response_data):\n+        self.assertEqual(instance.id, response_data['id'])\n+        self.assertEqual(instance.patch.id, response_data['patch']['id'])\n+        self.assertEqual(\n+            instance.submitter.id, response_data['submitter']['id']\n+        )\n+\n+    def test_create_note(self):\n+        start_num = Note.objects.count()\n+        url = reverse(\n+            'api-patch-note-list', kwargs={'patch_id': self.patch.id}\n+        )\n+        data = {'content': 'New note'}\n+        self.client.authenticate(user=self.user)\n+        resp = self.client.post(url, data=data)\n+        end_num = Note.objects.count()\n+\n+        self.assertEqual(status.HTTP_201_CREATED, resp.status_code)\n+        self.assertEqual(start_num + 1, end_num)\n+\n+    def test_get_note_as_maintainer(self):\n+        \"\"\"Retrieve patch note with an user that is a maintainer.\"\"\"\n+        person = create_person(user=self.user)\n+        note = create_note(patch=self.patch, submitter=person)\n+\n+        self.client.authenticate(user=self.user)\n+        url = reverse(\n+            'api-patch-note-detail',\n+            kwargs={'patch_id': self.patch.id, 'note_id': note.id},\n+        )\n+        resp = self.client.get(url)\n+\n+        self.assertEqual(status.HTTP_200_OK, resp.status_code)\n+        self.check_for_expected(note, resp.data)\n+\n+    def test_get_note_as_non_maintainer(self):\n+        \"\"\"Retrieve patch note with an user that is not a maintainer.\"\"\"\n+        person = create_person()\n+        note = create_note(patch=self.patch)\n+\n+        self.client.authenticate(user=person.user)\n+        url = reverse(\n+            'api-patch-note-detail',\n+            kwargs={'patch_id': self.patch.id, 'note_id': note.id},\n+        )\n+        resp = self.client.get(url)\n+\n+        self.assertEqual(status.HTTP_403_FORBIDDEN, resp.status_code)\n+\n+    def test_get_note_public(self):\n+        \"\"\"Retrieve public patch note with an user that is not a maintainer.\"\"\"\n+        person = create_person(user=self.user)\n+        note = create_note(patch=self.patch, maintainer_only=False)\n+\n+        self.client.authenticate(user=person.user)\n+        url = reverse(\n+            'api-patch-note-detail',\n+            kwargs={'patch_id': self.patch.id, 'note_id': note.id},\n+        )\n+        resp = self.client.get(url)\n+\n+        self.assertEqual(status.HTTP_200_OK, resp.status_code)\n+        self.check_for_expected(note, resp.data)\n+\n+    def test_get_note_list_as_maintainer(self):\n+        \"\"\"Retrieve notes from a patch note with an user that is a maintainer.\"\"\"\n+        person = create_person(user=self.user)\n+        create_note(patch=self.patch, submitter=person)\n+        create_note(patch=self.patch, submitter=person, maintainer_only=False)\n+\n+        self.client.authenticate(user=self.user)\n+        url = reverse(\n+            'api-patch-note-list', kwargs={'patch_id': self.patch.id}\n+        )\n+        resp = self.client.get(url)\n+\n+        self.assertEqual(status.HTTP_200_OK, resp.status_code)\n+        self.assertEqual(len(resp.data), 2)\n+\n+    def test_get_note_list_as_non_maintainer(self):\n+        \"\"\"Retrieve notes from a patch note with an user that is not a maintainer.\"\"\"\n+        person = create_person(user=self.user)\n+        create_note(patch=self.patch, submitter=person)\n+        public_note = create_note(\n+            patch=self.patch, submitter=person, maintainer_only=False\n+        )\n+        not_maintainer = create_user()\n+\n+        self.client.authenticate(user=not_maintainer)\n+        url = reverse(\n+            'api-patch-note-list', kwargs={'patch_id': self.patch.id}\n+        )\n+        resp = self.client.get(url)\n+\n+        self.assertEqual(status.HTTP_200_OK, resp.status_code)\n+        self.assertEqual(len(resp.data), 1)\n+        self.assertEqual(resp.data[0]['id'], public_note.id)\n+\n+    def test_edit_note_as_maintainer(self):\n+        \"\"\"Edit patch note with an user that is a maintainer.\"\"\"\n+        person = create_person(user=self.user)\n+        note = create_note(patch=self.patch, submitter=person)\n+\n+        url = reverse(\n+            'api-patch-note-detail',\n+            kwargs={'patch_id': self.patch.id, 'note_id': note.id},\n+        )\n+        data = {'content': 'New content'}\n+        self.client.authenticate(user=person.user)\n+        resp = self.client.patch(url, data=data)\n+\n+        self.assertEqual(status.HTTP_200_OK, resp.status_code)\n+        self.check_for_expected(note, resp.data)\n+        self.assertNotEqual(note.content, resp.data['content'])\n+        self.assertNotEqual(note.last_modified, resp.data['last_modified'])\n+\n+    def test_edit_note_as_non_maintainer(self):\n+        \"\"\"Edit patch note with an user that is not a maintainer.\"\"\"\n+        person = create_person()\n+        note = create_note(patch=self.patch)\n+\n+        url = reverse(\n+            'api-patch-note-detail',\n+            kwargs={'patch_id': self.patch.id, 'note_id': note.id},\n+        )\n+        data = {'content': 'New content'}\n+        self.client.authenticate(user=person.user)\n+        resp = self.client.patch(url, data=data)\n+\n+        self.assertEqual(status.HTTP_403_FORBIDDEN, resp.status_code)\n+\n+    def test_edit_note_public(self):\n+        \"\"\"Edit public patch note with an user that is a maintainerwith an user that is not a maintainer.\"\"\"\n+        person = create_person()\n+        note = create_note(patch=self.patch, maintainer_only=False)\n+\n+        url = reverse(\n+            'api-patch-note-detail',\n+            kwargs={'patch_id': self.patch.id, 'note_id': note.id},\n+        )\n+        data = {'content': 'New content'}\n+        self.client.authenticate(user=person.user)\n+        resp = self.client.patch(url, data=data)\n+\n+        self.assertEqual(status.HTTP_403_FORBIDDEN, resp.status_code)\n+\n+    def test_delete_note_as_maintainer(self):\n+        \"\"\"Delete patch note with an user that is a maintainer.\"\"\"\n+        person = create_person(user=self.user)\n+        note = create_note(patch=self.patch, submitter=person)\n+        start_num = Note.objects.count()\n+\n+        url = reverse(\n+            'api-patch-note-detail',\n+            kwargs={'patch_id': self.patch.id, 'note_id': note.id},\n+        )\n+\n+        self.client.authenticate(user=person.user)\n+        resp = self.client.delete(url)\n+        end_num = Note.objects.count()\n+\n+        self.assertEqual(status.HTTP_204_NO_CONTENT, resp.status_code)\n+        self.assertEqual(start_num - 1, end_num)\n+\n+    def test_delete_note_as_non_maintainer(self):\n+        \"\"\"Delete patch note with an user that is not a maintainer.\"\"\"\n+        person = create_person()\n+        note = create_note(patch=self.patch)\n+\n+        url = reverse(\n+            'api-patch-note-detail',\n+            kwargs={'patch_id': self.patch.id, 'note_id': note.id},\n+        )\n+\n+        self.client.authenticate(user=person.user)\n+        resp = self.client.delete(url)\n+\n+        self.assertEqual(status.HTTP_403_FORBIDDEN, resp.status_code)\n+\n+    def test_delete_note_public(self):\n+        \"\"\"Delete public patch note with an user that is a maintainerwith an user that is not a maintainer.\"\"\"\n+        person = create_person()\n+        note = create_note(patch=self.patch, maintainer_only=False)\n+\n+        url = reverse(\n+            'api-patch-note-detail',\n+            kwargs={'patch_id': self.patch.id, 'note_id': note.id},\n+        )\n+        self.client.authenticate(user=person.user)\n+        resp = self.client.delete(url)\n+\n+        self.assertEqual(status.HTTP_403_FORBIDDEN, resp.status_code)\n+\n+    def test_notes_in_patch(self):\n+        url = reverse('api-patch-detail', kwargs={'pk': self.patch.id})\n+        self.client.authenticate(user=self.user)\n+        resp = self.client.get(url)\n+\n+        correct_path = reverse(\n+            'api-patch-note-list', kwargs={'patch_id': self.patch.id}\n+        )\n+        self.assertEqual(\n+            resp.data.get('notes'),\n+            f'http://example.com{correct_path}',\n+        )\ndiff --git a/patchwork/tests/utils.py b/patchwork/tests/utils.py\nindex 4f40489..3fbb689 100644\n--- a/patchwork/tests/utils.py\n+++ b/patchwork/tests/utils.py\n@@ -15,6 +15,7 @@ from patchwork.models import Bundle\n from patchwork.models import Check\n from patchwork.models import Cover\n from patchwork.models import CoverComment\n+from patchwork.models import Note\n from patchwork.models import Patch\n from patchwork.models import PatchComment\n from patchwork.models import PatchRelation\n@@ -270,6 +271,17 @@ def create_patch_comment(**kwargs):\n     return PatchComment.objects.create(**values)\n \n \n+def create_note(**kwargs):\n+    \"\"\"Create 'Note' object.\"\"\"\n+    values = {\n+        'submitter': create_person() if 'submitter' not in kwargs else None,\n+        'content': 'Note content',\n+    }\n+    values.update(kwargs)\n+\n+    return Note.objects.create(**values)\n+\n+\n def create_check(**kwargs):\n     \"\"\"Create 'Check' object.\"\"\"\n     values = {\n",
    "prefixes": [
        "6/9"
    ]
}