{"id":1270129,"url":"http://patchwork.ozlabs.org/api/patches/1270129/","web_url":"http://patchwork.ozlabs.org/project/patchwork/patch/20200414062102.6798-3-dja@axtens.net/","project":{"id":16,"url":"http://patchwork.ozlabs.org/api/projects/16/","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":"<20200414062102.6798-3-dja@axtens.net>","list_archive_url":null,"date":"2020-04-14T06:21:02","name":"[2/2] api: allow filtering patches and covers by msgid","commit_ref":null,"pull_url":null,"state":"accepted","archived":false,"hash":"35621a9b0e149d7ee9cbb626991811d508ea7a25","submitter":{"id":65792,"url":"http://patchwork.ozlabs.org/api/people/65792/","name":"Daniel Axtens","email":"dja@axtens.net"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/patchwork/patch/20200414062102.6798-3-dja@axtens.net/mbox/","series":[{"id":170086,"url":"http://patchwork.ozlabs.org/api/series/170086/","web_url":"http://patchwork.ozlabs.org/project/patchwork/list/?series=170086","date":"2020-04-14T06:21:00","name":"v2.2 fixups for OzLabs","version":1,"mbox":"http://patchwork.ozlabs.org/series/170086/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/1270129/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/1270129/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@bilbo.ozlabs.org","patchwork@lists.ozlabs.org"],"Received":["from lists.ozlabs.org (lists.ozlabs.org [203.11.71.2])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange X25519 server-signature RSA-PSS (4096 bits))\n\t(No client certificate requested)\n\tby ozlabs.org (Postfix) with ESMTPS id 491b4T4gh0z9s71\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 14 Apr 2020 16:22:17 +1000 (AEST)","from lists.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3])\n\tby lists.ozlabs.org (Postfix) with ESMTP id 491b4R4Vg8zDqWw\n\tfor <incoming@patchwork.ozlabs.org>; Tue, 14 Apr 2020 16:22:15 +1000 (AEST)","from mail-pj1-x1041.google.com (mail-pj1-x1041.google.com\n [IPv6:2607:f8b0:4864:20::1041])\n (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest\n SHA256)\n (No client certificate requested)\n by lists.ozlabs.org (Postfix) with ESMTPS id 491b3K3xjzzDqTL\n for <patchwork@lists.ozlabs.org>; Tue, 14 Apr 2020 16:21:17 +1000 (AEST)","by mail-pj1-x1041.google.com with SMTP id mn19so4798786pjb.0\n for <patchwork@lists.ozlabs.org>; Mon, 13 Apr 2020 23:21:17 -0700 (PDT)","from localhost\n (2001-44b8-1113-6700-65ed-ce52-e9b0-0e8c.static.ipv6.internode.on.net.\n [2001:44b8:1113:6700:65ed:ce52:e9b0:e8c])\n by smtp.gmail.com with ESMTPSA id q63sm1127445pfb.178.2020.04.13.23.21.12\n (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n Mon, 13 Apr 2020 23:21:13 -0700 (PDT)"],"Authentication-Results":["ozlabs.org;\n dmarc=none (p=none dis=none) header.from=axtens.net","ozlabs.org;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n unprotected) header.d=axtens.net header.i=@axtens.net header.a=rsa-sha256\n header.s=google header.b=iPX+Yz5D;\n\tdkim-atps=neutral","lists.ozlabs.org; spf=pass (sender SPF authorized)\n smtp.mailfrom=axtens.net (client-ip=2607:f8b0:4864:20::1041;\n helo=mail-pj1-x1041.google.com; envelope-from=dja@axtens.net;\n receiver=<UNKNOWN>)","lists.ozlabs.org;\n dmarc=none (p=none dis=none) header.from=axtens.net","lists.ozlabs.org; dkim=pass (1024-bit key;\n unprotected) header.d=axtens.net header.i=@axtens.net header.a=rsa-sha256\n header.s=google header.b=iPX+Yz5D; dkim-atps=neutral"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=axtens.net; s=google;\n h=from:to:cc:subject:date:message-id:in-reply-to:references\n :mime-version:content-transfer-encoding;\n bh=mi1ZClZg5nzBCZ/0N8U9CBWAZlsb9xwB81QDZX+eBuI=;\n b=iPX+Yz5DoQFXNjLneM+UykOQc7uejn+FtjwMRHLtgut+e/jxCJbtNemmrkVA/38U2c\n t1X02TT81sNaymW1DemjmjPoPxu0sSLAt2iXzpVmIRzRyPXCWZ9JLZtM6ZNk0S/+5nSY\n 3ba8ICmCig4O8cjqP1Q0GGrlXRwLpMkFQFnyc=","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20161025;\n h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to\n :references:mime-version:content-transfer-encoding;\n bh=mi1ZClZg5nzBCZ/0N8U9CBWAZlsb9xwB81QDZX+eBuI=;\n b=pMOQ7Z/fBuci5O1AIGPRG8yrxE8aVhppZhlTq0PBcpzTS4OHOQYxj4Kkt6Kt0/YAx2\n 8XyObKi9pd24dnQXT+rjCPJwOSJmdgsebkAlxqLAjJTJ4uIn6GAgHxFCVGBhzfQn5Vyo\n sWnv/0TT/ZgykLij+fxJwEr/HusblwcAz9QHZG+4xpNsiEjZbe2Xzt7t2soKQCoOeTBn\n j8aMFjsKvIrXEonqMP5QnLmKQgv7A0PWb5i/x7zXnJCps5nuP+OyPAhfg1f1zAM36/J6\n bj5LRKvmwj5EpXoWsD96DmepKSX1MS/7ghTbhe/8dTqQ91YB0cIiOyMNYVg0VMDxmfcs\n OnBA==","X-Gm-Message-State":"AGi0PuZugW2vb33+enhMGegPT26lJe7zS5X5y8YwO52uTM97AgxG5N9T\n T6DB7BZ3tVaeAdPV+jx7coDZlOO7wTY=","X-Google-Smtp-Source":"\n APiQypJl2Ras5wETAIEw9orW1mrS/xrrpA+5lVhQCCpl5FtdnQf/WL3Ye4PbUbW9btVApAmLXuaqPg==","X-Received":"by 2002:a17:90a:364c:: with SMTP id\n s70mr25652975pjb.143.1586845274133;\n Mon, 13 Apr 2020 23:21:14 -0700 (PDT)","From":"Daniel Axtens <dja@axtens.net>","To":"patchwork@lists.ozlabs.org","Subject":"[PATCH 2/2] api: allow filtering patches and covers by msgid","Date":"Tue, 14 Apr 2020 16:21:02 +1000","Message-Id":"<20200414062102.6798-3-dja@axtens.net>","X-Mailer":"git-send-email 2.20.1","In-Reply-To":"<20200414062102.6798-1-dja@axtens.net>","References":"<20200414062102.6798-1-dja@axtens.net>","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>","Cc":"Michael Ellerman <mpe@ellerman.id.au>, jk@ozlabs.org","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":"In the process of fixing the previous bug, I realised that:\n\n a) /api/patches/msgid is a perfectly reasonable thing to attempt\n b) We have no way of finding a patch by message id in the API\n\nWe can't actualy make /api/patches/msgid work because it may not\nbe unique, but we can add a filter.\n\nI'm shoehorning this into stable/2.2, even though it's technically\nan API change: it's minor, not incompatible and in hindsight a\nglaring hole.\n\nCc: Michael Ellerman <mpe@ellerman.id.au>\nSigned-off-by: Daniel Axtens <dja@axtens.net>\n---\n docs/api/schemas/latest/patchwork.yaml         | 16 ++++++++++++++++\n docs/api/schemas/patchwork.j2                  | 18 ++++++++++++++++++\n docs/api/schemas/v1.2/patchwork.yaml           | 16 ++++++++++++++++\n patchwork/api/filters.py                       | 14 ++++++++++----\n patchwork/tests/api/test_cover.py              | 12 ++++++++++++\n patchwork/tests/api/test_patch.py              | 12 ++++++++++++\n .../rest-filter-msgid-41f693cd4e53cf93.yaml    |  6 ++++++\n 7 files changed, 90 insertions(+), 4 deletions(-)\n create mode 100644 releasenotes/notes/rest-filter-msgid-41f693cd4e53cf93.yaml","diff":"diff --git a/docs/api/schemas/latest/patchwork.yaml b/docs/api/schemas/latest/patchwork.yaml\nindex 13cdc9cd78fd..cc0d97e696b6 100644\n--- a/docs/api/schemas/latest/patchwork.yaml\n+++ b/docs/api/schemas/latest/patchwork.yaml\n@@ -246,6 +246,14 @@ paths:\n           schema:\n             title: ''\n             type: string\n+        - in: query\n+          name: msgid\n+          description: >\n+            The cover message-id as a case-sensitive string, without leading or\n+            trailing angle brackets, to filter by.\n+          schema:\n+            title: ''\n+            type: string\n       responses:\n         '200':\n           description: ''\n@@ -474,6 +482,14 @@ paths:\n           schema:\n             title: ''\n             type: string\n+        - in: query\n+          name: msgid\n+          description: >\n+            The patch message-id as a case-sensitive string, without leading or\n+            trailing angle brackets, to filter by.\n+          schema:\n+            title: ''\n+            type: string\n       responses:\n         '200':\n           description: ''\ndiff --git a/docs/api/schemas/patchwork.j2 b/docs/api/schemas/patchwork.j2\nindex bd714d5e7a2a..f5618d41faa0 100644\n--- a/docs/api/schemas/patchwork.j2\n+++ b/docs/api/schemas/patchwork.j2\n@@ -251,6 +251,16 @@ paths:\n           schema:\n             title: ''\n             type: string\n+{% if version >= (1, 2) %}\n+        - in: query\n+          name: msgid\n+          description: >\n+            The cover message-id as a case-sensitive string, without leading or\n+            trailing angle brackets, to filter by.\n+          schema:\n+            title: ''\n+            type: string\n+{% endif %}\n       responses:\n         '200':\n           description: ''\n@@ -488,6 +498,14 @@ paths:\n           schema:\n             title: ''\n             type: string\n+        - in: query\n+          name: msgid\n+          description: >\n+            The patch message-id as a case-sensitive string, without leading or\n+            trailing angle brackets, to filter by.\n+          schema:\n+            title: ''\n+            type: string\n {% endif %}\n       responses:\n         '200':\ndiff --git a/docs/api/schemas/v1.2/patchwork.yaml b/docs/api/schemas/v1.2/patchwork.yaml\nindex db2ed122eec2..7bdbe66997c0 100644\n--- a/docs/api/schemas/v1.2/patchwork.yaml\n+++ b/docs/api/schemas/v1.2/patchwork.yaml\n@@ -246,6 +246,14 @@ paths:\n           schema:\n             title: ''\n             type: string\n+        - in: query\n+          name: msgid\n+          description: >\n+            The cover message-id as a case-sensitive string, without leading or\n+            trailing angle brackets, to filter by.\n+          schema:\n+            title: ''\n+            type: string\n       responses:\n         '200':\n           description: ''\n@@ -474,6 +482,14 @@ paths:\n           schema:\n             title: ''\n             type: string\n+        - in: query\n+          name: msgid\n+          description: >\n+            The patch message-id as a case-sensitive string, without leading or\n+            trailing angle brackets, to filter by.\n+          schema:\n+            title: ''\n+            type: string\n       responses:\n         '200':\n           description: ''\ndiff --git a/patchwork/api/filters.py b/patchwork/api/filters.py\nindex deb5ace11880..93e6281bf5e6 100644\n--- a/patchwork/api/filters.py\n+++ b/patchwork/api/filters.py\n@@ -184,6 +184,10 @@ class SeriesFilterSet(TimestampMixin, BaseFilterSet):\n         fields = ('submitter', 'project')\n \n \n+def msgid_filter(queryset, name, value):\n+    return queryset.filter(**{name: '<' + value + '>'})\n+\n+\n class CoverLetterFilterSet(TimestampMixin, BaseFilterSet):\n \n     project = ProjectFilter(queryset=Project.objects.all(), distinct=False)\n@@ -192,6 +196,7 @@ class CoverLetterFilterSet(TimestampMixin, BaseFilterSet):\n     series = BaseFilter(queryset=Project.objects.all(),\n                         widget=MultipleHiddenInput, distinct=False)\n     submitter = PersonFilter(queryset=Person.objects.all(), distinct=False)\n+    msgid = CharFilter(method=msgid_filter)\n \n     class Meta:\n         model = CoverLetter\n@@ -210,17 +215,18 @@ class PatchFilterSet(TimestampMixin, BaseFilterSet):\n     delegate = UserFilter(queryset=User.objects.all(), distinct=False)\n     state = StateFilter(queryset=State.objects.all(), distinct=False)\n     hash = CharFilter(lookup_expr='iexact')\n+    msgid = CharFilter(method=msgid_filter)\n \n     class Meta:\n         model = Patch\n-        # NOTE(dja): ideally we want to version the hash field, but I cannot\n-        # find a way to do that which is reliable and not extremely ugly.\n+        # NOTE(dja): ideally we want to version the hash/msgid field, but I\n+        # can't find a way to do that which is reliable and not extremely ugly.\n         # The best I can come up with is manually working with request.GET\n         # which seems to rather defeat the point of using django-filters.\n         fields = ('project', 'series', 'submitter', 'delegate',\n-                  'state', 'archived', 'hash')\n+                  'state', 'archived', 'hash', 'msgid')\n         versioned_fields = {\n-            '1.2': ('hash', ),\n+            '1.2': ('hash', 'msgid'),\n         }\n \n \ndiff --git a/patchwork/tests/api/test_cover.py b/patchwork/tests/api/test_cover.py\nindex 5eeb1902e1d1..1b19ded1b4d5 100644\n--- a/patchwork/tests/api/test_cover.py\n+++ b/patchwork/tests/api/test_cover.py\n@@ -111,6 +111,18 @@ class TestCoverLetterAPI(utils.APITestCase):\n             'submitter': 'test@example.org'})\n         self.assertEqual(0, len(resp.data))\n \n+    def test_list_filter_msgid(self):\n+        \"\"\"Filter covers by msgid.\"\"\"\n+        cover = create_cover()\n+\n+        resp = self.client.get(self.api_url(), {'msgid': cover.url_msgid})\n+        self.assertEqual([cover.id], [x['id'] for x in resp.data])\n+\n+        # empty response if nothing matches\n+        resp = self.client.get(self.api_url(), {\n+            'msgid': 'fishfish@fish.fish'})\n+        self.assertEqual(0, len(resp.data))\n+\n     @utils.store_samples('cover-list-1-0')\n     def test_list_version_1_0(self):\n         create_cover()\ndiff --git a/patchwork/tests/api/test_patch.py b/patchwork/tests/api/test_patch.py\nindex b24c5ab28947..da2dd6e9084b 100644\n--- a/patchwork/tests/api/test_patch.py\n+++ b/patchwork/tests/api/test_patch.py\n@@ -199,6 +199,18 @@ class TestPatchAPI(utils.APITestCase):\n                                {'hash': 'garbagevalue'})\n         self.assertEqual(1, len(resp.data))\n \n+    def test_list_filter_msgid(self):\n+        \"\"\"Filter patches by msgid.\"\"\"\n+        patch = self._create_patch()\n+\n+        resp = self.client.get(self.api_url(), {'msgid': patch.url_msgid})\n+        self.assertEqual([patch.id], [x['id'] for x in resp.data])\n+\n+        # empty response if nothing matches\n+        resp = self.client.get(self.api_url(), {\n+            'msgid': 'fishfish@fish.fish'})\n+        self.assertEqual(0, len(resp.data))\n+\n     @utils.store_samples('patch-list-1-0')\n     def test_list_version_1_0(self):\n         \"\"\"List patches using API v1.0.\"\"\"\ndiff --git a/releasenotes/notes/rest-filter-msgid-41f693cd4e53cf93.yaml b/releasenotes/notes/rest-filter-msgid-41f693cd4e53cf93.yaml\nnew file mode 100644\nindex 000000000000..0fcbbeb8a736\n--- /dev/null\n+++ b/releasenotes/notes/rest-filter-msgid-41f693cd4e53cf93.yaml\n@@ -0,0 +1,6 @@\n+---\n+api:\n+  - |\n+    The REST API now supports filtering patches and cover letters by message\n+    ID, using the ``msgid`` query parameter. Don't include leading or trailing\n+    angle brackets.\n","prefixes":["2/2"]}