From patchwork Fri Sep 30 16:19:12 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Finucane X-Patchwork-Id: 1684919 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.ozlabs.org (client-ip=112.213.38.117; helo=lists.ozlabs.org; envelope-from=patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=ueD4rLEx; dkim-atps=neutral Received: from lists.ozlabs.org (lists.ozlabs.org [112.213.38.117]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4MfFn04QDbz1yql for ; Sat, 1 Oct 2022 02:19:48 +1000 (AEST) Received: from boromir.ozlabs.org (localhost [IPv6:::1]) by lists.ozlabs.org (Postfix) with ESMTP id 4MfFmz1Bpkz3cFl for ; Sat, 1 Oct 2022 02:19:47 +1000 (AEST) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=ueD4rLEx; dkim-atps=neutral X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=none (no SPF record) smtp.mailfrom=that.guru (client-ip=136.175.108.191; helo=mail-108-mta191.mxroute.com; envelope-from=stephen@that.guru; receiver=) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=ueD4rLEx; dkim-atps=neutral Received: from mail-108-mta191.mxroute.com (mail-108-mta191.mxroute.com [136.175.108.191]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 4MfFmn58kzz3c61 for ; Sat, 1 Oct 2022 02:19:36 +1000 (AEST) Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta191.mxroute.com (ZoneMTA) with ESMTPSA id 1838f32149c0002b7a.001 for (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Fri, 30 Sep 2022 16:19:27 +0000 X-Zone-Loop: 99abe8ffae8e522bb5ea0760cc649191b18161efcc44 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=that.guru; s=x; h=Content-Transfer-Encoding:MIME-Version:Message-Id:Date:Subject:Cc:To: From:Sender:Reply-To:Content-Type:Content-ID:Content-Description:Resent-Date: Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To: References:List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post: List-Owner:List-Archive; bh=DiOEsKe/oDn5ZrTklP6rG4lEOlUMT9HMiv+Ie67hul0=; b=u eD4rLExI9LDOlGEgwk8L7GBfKpNh55zbHFX5UjM5c0S+bOisid9EOGRtooC4br5udeNHxlbBUqFJL Ux0Tnrcz3N5Uu1Z4WxeBb2h5nVoay31bnRX5XIvGdVk1zOLaKiF4zNTc3fZDRMCQdX/AbV/6bTdhM k3ydq2+6Qh+uH///E9bjbWVCH8Zvw8jenNnKWtFtYQNyEIJ/EMb7qC1XISG3TeDX7bRYkX6IdTqFM mNlnWRwr5p3sf7jOwla2Fx2jnpVQrkEhg6Y90UW/h5DHooEy8lIZC5TfVLZYx5ze4rS6/qhAK38iY T06DEwmNPGtoi+BFS961S8ghbSeoF9pEw==; From: Stephen Finucane To: patchwork@lists.ozlabs.org Subject: [PATCH 01/10] tests: Change from expectedFailure to skip Date: Fri, 30 Sep 2022 17:19:12 +0100 Message-Id: <20220930161921.266633-1-stephen@that.guru> MIME-Version: 1.0 X-Authenticated-Id: stephen@that.guru X-BeenThere: patchwork@lists.ozlabs.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Patchwork development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" Python 3.10 recognises unexpected passes as failures now. Signed-off-by: Stephen Finucane --- patchwork/tests/test_series.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git patchwork/tests/test_series.py patchwork/tests/test_series.py index 8d8c4e14..d3e20e08 100644 --- patchwork/tests/test_series.py +++ patchwork/tests/test_series.py @@ -178,7 +178,7 @@ class BaseSeriesTest(_BaseTestCase): self.assertSerialized(patches, [2]) self.assertSerialized(covers, [1]) - @unittest.expectedFailure + @unittest.skip('Flaky test') def test_duplicated(self): """Series received on multiple mailing lists. From patchwork Fri Sep 30 16:19:13 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Stephen Finucane X-Patchwork-Id: 1684918 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.ozlabs.org (client-ip=2404:9400:2:0:216:3eff:fee1:b9f1; helo=lists.ozlabs.org; envelope-from=patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=cwNDnp2R; dkim-atps=neutral Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2404:9400:2:0:216:3eff:fee1:b9f1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4MfFmv6Vr1z1yql for ; Sat, 1 Oct 2022 02:19:43 +1000 (AEST) Received: from boromir.ozlabs.org (localhost [IPv6:::1]) by lists.ozlabs.org (Postfix) with ESMTP id 4MfFmt5Xppz3c72 for ; Sat, 1 Oct 2022 02:19:42 +1000 (AEST) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=cwNDnp2R; dkim-atps=neutral X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=none (no SPF record) smtp.mailfrom=that.guru (client-ip=136.175.108.15; helo=mail-108-mta15.mxroute.com; envelope-from=stephen@that.guru; receiver=) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=cwNDnp2R; dkim-atps=neutral Received: from mail-108-mta15.mxroute.com (mail-108-mta15.mxroute.com [136.175.108.15]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 4MfFmn5Gdvz3c6C for ; Sat, 1 Oct 2022 02:19:36 +1000 (AEST) Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta15.mxroute.com (ZoneMTA) with ESMTPSA id 1838f3216ca0002b7a.001 for (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Fri, 30 Sep 2022 16:19:28 +0000 X-Zone-Loop: 446016b8e26c426bfe241bf57cf9e3cd640530295e10 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=that.guru; s=x; h=Content-Transfer-Encoding:Content-Type:MIME-Version:References: In-Reply-To:Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=8wDQfkJ9Z1WUS4jYTkfWzOsynUlTpPi3120FaU7+UgY=; b=cwNDnp2Rd/dGP1TnGvpu5QctT3 KPxAdxCEQ2QcLBsBXB1tyQMLTsJmv88+QkV1nvWjJi3jde4qTo5LDepIqlgXEXIE9sq2hwVVE9p9a vb8ivXa3tRnuDDj1fkLHfBB1o1Qwd4Iiet0hkpq9ZpG1v5052lFj2wjgkKLxdvPLd7pti+3yg3Phl 8A2dGjBV3GRzunMaPb4z0GHyMpGcfWe9d0WZZfZkXvC7W2aSBiZAheq+MEOC8ohvShCq9d9zBxjYj TGTH6O9z0J88lTczqOVTw9+W+TLPOzKclGfyL/Na894wZZYwoLVTg74YRr3V/lzOvnPSHfhZ1AxD4 dOu0+UnQ==; From: Stephen Finucane To: patchwork@lists.ozlabs.org Subject: [PATCH 02/10] trivial: Remove unnecessary unicode prefixes Date: Fri, 30 Sep 2022 17:19:13 +0100 Message-Id: <20220930161921.266633-2-stephen@that.guru> In-Reply-To: <20220930161921.266633-1-stephen@that.guru> References: <20220930161921.266633-1-stephen@that.guru> MIME-Version: 1.0 X-Authenticated-Id: stephen@that.guru X-BeenThere: patchwork@lists.ozlabs.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Patchwork development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" Signed-off-by: Stephen Finucane --- docs/conf.py | 6 +++--- patchwork/forms.py | 8 +++++--- patchwork/tests/test_parser.py | 12 ++++++------ patchwork/tests/views/test_mail.py | 16 ++++++++-------- patchwork/tests/views/test_patch.py | 2 +- patchwork/tests/views/test_utils.py | 6 +++--- 6 files changed, 26 insertions(+), 24 deletions(-) diff --git docs/conf.py docs/conf.py index 30a6b006..0b303c73 100644 --- docs/conf.py +++ docs/conf.py @@ -29,9 +29,9 @@ html_theme = 'sphinx_rtd_theme' master_doc = 'index' # General information about the project. -project = u'Patchwork' -copyright = u'2018-2019, Patchwork Developers' -author = u'Patchwork Developers' +project = 'Patchwork' +copyright = '2018-, Patchwork Developers' +author = 'Patchwork Developers' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git patchwork/forms.py patchwork/forms.py index 7c9781af..84448586 100644 --- patchwork/forms.py +++ patchwork/forms.py @@ -18,9 +18,11 @@ class RegistrationForm(forms.Form): first_name = forms.CharField(max_length=30, required=False) last_name = forms.CharField(max_length=30, required=False) username = forms.RegexField( - regex=r'^\w+$', max_length=30, label=u'Username' + regex=r'^\w+$', + max_length=30, + label='Username', ) - email = forms.EmailField(max_length=100, label=u'Email address') + email = forms.EmailField(max_length=100, label='Email address') password = forms.CharField(widget=forms.PasswordInput(), label='Password') def clean_username(self): @@ -57,7 +59,7 @@ class BundleForm(forms.ModelForm): regex=r'^[^/]+$', min_length=1, max_length=50, - label=u'Name', + label='Name', error_messages={'invalid': 'Bundle names can\'t contain slashes'}, ) diff --git patchwork/tests/test_parser.py patchwork/tests/test_parser.py index 108d8b9b..b731fe78 100644 --- patchwork/tests/test_parser.py +++ patchwork/tests/test_parser.py @@ -279,25 +279,25 @@ class SenderEncodingTest(TestCase): def test_ascii_encoding(self): from_header = 'example user ' - sender_name = u'example user' + sender_name = 'example user' sender_email = 'user@example.com' self._test_encoding(from_header, sender_name, sender_email) def test_utf8qp_encoding(self): from_header = '=?utf-8?q?=C3=A9xample=20user?= ' - sender_name = u'\xe9xample user' + sender_name = '\xe9xample user' sender_email = 'user@example.com' self._test_encoding(from_header, sender_name, sender_email) def test_utf8qp_split_encoding(self): from_header = '=?utf-8?q?=C3=A9xample?= user ' - sender_name = u'\xe9xample user' + sender_name = '\xe9xample user' sender_email = 'user@example.com' self._test_encoding(from_header, sender_name, sender_email) def test_utf8b64_encoding(self): from_header = '=?utf-8?B?w6l4YW1wbGUgdXNlcg==?= ' - sender_name = u'\xe9xample user' + sender_name = '\xe9xample user' sender_email = 'user@example.com' self._test_encoding(from_header, sender_name, sender_email) @@ -552,12 +552,12 @@ class SubjectEncodingTest(TestCase): def test_subject_utf8qp_encoding(self): subject_header = '=?utf-8?q?test=20s=c3=bcbject?=' - subject = u'test s\xfcbject' + subject = 'test s\xfcbject' self._test_encoding(subject_header, subject) def test_subject_utf8qp_multiple_encoding(self): subject_header = 'test =?utf-8?q?s=c3=bcbject?=' - subject = u'test s\xfcbject' + subject = 'test s\xfcbject' self._test_encoding(subject_header, subject) diff --git patchwork/tests/views/test_mail.py patchwork/tests/views/test_mail.py index f2b19973..de9df3d2 100644 --- patchwork/tests/views/test_mail.py +++ patchwork/tests/views/test_mail.py @@ -23,7 +23,7 @@ class MailSettingsTest(TestCase): self.assertTrue(response.context['form']) def test_post(self): - email = u'foo@example.com' + email = 'foo@example.com' response = self.client.post(reverse('mail-settings'), {'email': email}) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'patchwork/mail-settings.html') @@ -44,7 +44,7 @@ class MailSettingsTest(TestCase): self.assertFormError(response, 'form', 'email', error_strings['email']) def test_post_optin(self): - email = u'foo@example.com' + email = 'foo@example.com' response = self.client.post(reverse('mail-settings'), {'email': email}) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'patchwork/mail-settings.html') @@ -53,7 +53,7 @@ class MailSettingsTest(TestCase): self.assertContains(response, 'action="%s"' % reverse('mail-optout')) def test_post_optout(self): - email = u'foo@example.com' + email = 'foo@example.com' EmailOptout(email=email).save() response = self.client.post(reverse('mail-settings'), {'email': email}) self.assertEqual(response.status_code, 200) @@ -69,7 +69,7 @@ class OptoutRequestTest(TestCase): self.assertRedirects(response, reverse('mail-settings')) def test_post(self): - email = u'foo@example.com' + email = 'foo@example.com' response = self.client.post(reverse('mail-optout'), {'email': email}) # check for a confirmation object @@ -109,7 +109,7 @@ class OptoutRequestTest(TestCase): class OptoutTest(TestCase): def setUp(self): - self.email = u'foo@example.com' + self.email = 'foo@example.com' self.conf = EmailConfirmation(type='optout', email=self.email) self.conf.save() @@ -139,7 +139,7 @@ class OptoutPreexistingTest(OptoutTest): class OptinRequestTest(TestCase): - email = u'foo@example.com' + email = 'foo@example.com' def setUp(self): EmailOptout(email=self.email).save() @@ -190,7 +190,7 @@ class OptinRequestTest(TestCase): class OptinTest(TestCase): def setUp(self): - self.email = u'foo@example.com' + self.email = 'foo@example.com' self.optout = EmailOptout(email=self.email) self.optout.save() self.conf = EmailConfirmation(type='optin', email=self.email) @@ -217,7 +217,7 @@ class OptinWithoutOptoutTest(TestCase): """Test an opt-in with no existing opt-out.""" def test_opt_in_without_optout(self): - email = u'foo@example.com' + email = 'foo@example.com' response = self.client.post(reverse('mail-optin'), {'email': email}) # check for an error message diff --git patchwork/tests/views/test_patch.py patchwork/tests/views/test_patch.py index 97b0e97e..9cea8a0b 100644 --- patchwork/tests/views/test_patch.py +++ patchwork/tests/views/test_patch.py @@ -530,6 +530,6 @@ class UTF8PatchViewTest(TestCase): class UTF8HeaderPatchViewTest(UTF8PatchViewTest): def setUp(self): - author = create_person(name=u'P\xe4tch Author') + author = create_person(name='P\xe4tch Author') patch_content = read_patch('0002-utf-8.patch', encoding='utf-8') self.patch = create_patch(submitter=author, diff=patch_content) diff --git patchwork/tests/views/test_utils.py patchwork/tests/views/test_utils.py index e10c3bde..6b980f9d 100644 --- patchwork/tests/views/test_utils.py +++ patchwork/tests/views/test_utils.py @@ -37,11 +37,11 @@ class MboxPatchResponseTest(TestCase): """Test that UTF-8 NBSP characters are correctly handled.""" patch = create_patch(content='patch text\n') create_patch_comment( - patch=patch, content=u'comment\nAcked-by:\u00A0 foo' + patch=patch, content='comment\nAcked-by:\u00A0 foo' ) mbox = utils.patch_to_mbox(patch) - self.assertIn(u'\u00A0 foo\n', mbox) + self.assertIn('\u00A0 foo\n', mbox) def test_multiple_tags(self): """Test that the mbox view appends tags correct. @@ -145,7 +145,7 @@ class MboxPatchResponseTest(TestCase): the format for the mail while the name part may be coded in some ways. """ - person = create_person(name=u'©ool guŷ') + person = create_person(name='©ool guŷ') patch = create_patch(submitter=person) from_email = f'<{person.email}>' mbox = utils.patch_to_mbox(patch) From patchwork Fri Sep 30 16:19:14 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Finucane X-Patchwork-Id: 1684920 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.ozlabs.org (client-ip=2404:9400:2:0:216:3eff:fee1:b9f1; helo=lists.ozlabs.org; envelope-from=patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=Q4SQYfgl; dkim-atps=neutral Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2404:9400:2:0:216:3eff:fee1:b9f1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4MfFn41Lwzz1yql for ; Sat, 1 Oct 2022 02:19:52 +1000 (AEST) Received: from boromir.ozlabs.org (localhost [IPv6:::1]) by lists.ozlabs.org (Postfix) with ESMTP id 4MfFn419QYz3cCc for ; Sat, 1 Oct 2022 02:19:52 +1000 (AEST) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=Q4SQYfgl; dkim-atps=neutral X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=none (no SPF record) smtp.mailfrom=that.guru (client-ip=136.175.108.154; helo=mail-108-mta154.mxroute.com; envelope-from=stephen@that.guru; receiver=) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=Q4SQYfgl; dkim-atps=neutral Received: from mail-108-mta154.mxroute.com (mail-108-mta154.mxroute.com [136.175.108.154]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 4MfFmn5Mgfz3c6N for ; Sat, 1 Oct 2022 02:19:36 +1000 (AEST) Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta154.mxroute.com (ZoneMTA) with ESMTPSA id 1838f3215650002b7a.001 for (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Fri, 30 Sep 2022 16:19:27 +0000 X-Zone-Loop: e2dc7b56dae15e80b06750917beea281d3b986d7625d X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=that.guru; s=x; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=wyME/Lps43KuDrdSng3iFNHjZWdASuAwFassr2LR358=; b=Q4SQYfglmarbdSn6m86PQzm5Fu r4zuj1RE/u9Zj7nO6pO/zzO7QvsQaxC4EywMjYRW+pIxp1Az2f3Ia8h/nujkLe7jtwGplnl0IX8nd V2QnPmU5vO4GNjvP895pLlk6q5HiwLehwI2I1FN8anMzt32LZAcYJGNfJMnYwDqwy1K2lFIqCWupN CA8WT5JWs4xjEo0p2wJ5DUFE+hm/6ckIin0f7ouKikDK/vMZTWgsA5l+ZCYzqL3Vqwf33peXB5jZT iClyh3ZHyuyeb8/LqVq06K88o92wP+2RDfDBQJFD2KdqVGAt3YfdDutjxuvvHzh66ePHT53xA1+cA dxva+iog==; From: Stephen Finucane To: patchwork@lists.ozlabs.org Subject: [PATCH 03/10] tox: Output test times, more verbose output Date: Fri, 30 Sep 2022 17:19:14 +0100 Message-Id: <20220930161921.266633-3-stephen@that.guru> In-Reply-To: <20220930161921.266633-1-stephen@that.guru> References: <20220930161921.266633-1-stephen@that.guru> MIME-Version: 1.0 X-Authenticated-Id: stephen@that.guru X-BeenThere: patchwork@lists.ozlabs.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Patchwork development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" Just a bit more useful for CI logs Signed-off-by: Stephen Finucane --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git tox.ini tox.ini index 0ab0ab84..aa434a72 100644 --- tox.ini +++ tox.ini @@ -24,7 +24,7 @@ passenv = DATABASE_TYPE DATABASE_USER DATABASE_PASSWORD DATABASE_HOST DATABASE_PORT DATABASE_NAME DJANGO_TEST_PROCESSES commands = - python {toxinidir}/manage.py test --noinput --parallel -- {posargs:patchwork} + python {toxinidir}/manage.py test --noinput --parallel -v 2 --timing -- {posargs:patchwork} [testenv:bashate] deps = bashate From patchwork Fri Sep 30 16:19:15 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Finucane X-Patchwork-Id: 1684922 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.ozlabs.org (client-ip=2404:9400:2:0:216:3eff:fee1:b9f1; helo=lists.ozlabs.org; envelope-from=patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=jbZGww7p; dkim-atps=neutral Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2404:9400:2:0:216:3eff:fee1:b9f1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4MfFnH5vPzz1yql for ; Sat, 1 Oct 2022 02:20:03 +1000 (AEST) Received: from boromir.ozlabs.org (localhost [IPv6:::1]) by lists.ozlabs.org (Postfix) with ESMTP id 4MfFnH5Q72z3c6v for ; Sat, 1 Oct 2022 02:20:03 +1000 (AEST) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=jbZGww7p; dkim-atps=neutral X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=none (no SPF record) smtp.mailfrom=that.guru (client-ip=136.175.108.99; helo=mail-108-mta99.mxroute.com; envelope-from=stephen@that.guru; receiver=) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=jbZGww7p; dkim-atps=neutral Received: from mail-108-mta99.mxroute.com (mail-108-mta99.mxroute.com [136.175.108.99]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 4MfFmp0Gd4z3c6W for ; Sat, 1 Oct 2022 02:19:36 +1000 (AEST) Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta99.mxroute.com (ZoneMTA) with ESMTPSA id 1838f321a0a0002b7a.001 for (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Fri, 30 Sep 2022 16:19:28 +0000 X-Zone-Loop: 4e6e407d52516d63fe77edc5d799f223559a10f535c1 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=that.guru; s=x; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=GxqT/4x1nQX/zXDGLnJV9FOekxLry5n/m3s3S8vPEB4=; b=jbZGww7pFEJXfcd+Xza6wwhMSg wdGloM/m9Xp2v632gFQOO+rxA5rI6sV2EGSdrr6sWrHsaawOP74CWTolbMFhJ919MPxTkMwTYsV3r iKiJwMvib9CklOaA+PxJ3T99aafMTJ/3cEPXgUOYKkjS6tI/vwZvZojAHmhd3FVsmKgr7d/9vDZvP VZnNxAU1D+RfU/QN5X91k9AMt8qfl5T95izdQac37tNmfDZVv8s1Qtdr2v6QXvgxUFEbN6IsMptHV mcfkOxXelj9081eDqB2Yl1f3FSlrOF3rwz24eqNf1X/q9O9sohjZocxjsv6q84SEFKuX8R8euQMuD n5/X7aDg==; From: Stephen Finucane To: patchwork@lists.ozlabs.org Subject: [PATCH 04/10] requirements: Bump Django to 3.2.x, djangorestframework to 4.16.0 Date: Fri, 30 Sep 2022 17:19:15 +0100 Message-Id: <20220930161921.266633-4-stephen@that.guru> In-Reply-To: <20220930161921.266633-1-stephen@that.guru> References: <20220930161921.266633-1-stephen@that.guru> MIME-Version: 1.0 X-Authenticated-Id: stephen@that.guru X-BeenThere: patchwork@lists.ozlabs.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Patchwork development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" There are two issues to be addressed: RemovedInDjango50Warning: Passing response to assertFormError() is deprecated. Use the form object directly: RemovedInDjango50Warning: The "default.html" templates for forms and formsets will be removed. These were proxies to the equivalent "table.html" templates, but the new "div.html" templates will be the default from Django 5.0. Transitional renderers are provided to allow you to opt-in to the new output style now. See https://docs.djangoproject.com/en/4.1/releases/4.1/ for more details Nothing complicated in fixing either of these. For the former, we must do as we're told and use the form object directly. For the latter, we need to configure our own form renderer so we can continue using the table form renderer for now. Signed-off-by: Stephen Finucane --- patchwork/forms.py | 8 ++ patchwork/settings/base.py | 2 + patchwork/tests/views/test_mail.py | 91 +++++++++++++--- patchwork/tests/views/test_patch.py | 23 ++-- patchwork/tests/views/test_user.py | 102 ++++++++++++++---- .../django-4-1-support-bcbe65a71d235b43.yaml | 5 + tox.ini | 9 +- 7 files changed, 197 insertions(+), 43 deletions(-) create mode 100644 releasenotes/notes/django-4-1-support-bcbe65a71d235b43.yaml diff --git patchwork/forms.py patchwork/forms.py index 84448586..1c5a29be 100644 --- patchwork/forms.py +++ patchwork/forms.py @@ -5,8 +5,10 @@ from django.contrib.auth.models import User from django import forms +from django.forms import renderers from django.db.models import Q from django.db.utils import ProgrammingError +from django.template.backends import django as django_template_backend from patchwork.models import Bundle from patchwork.models import Patch @@ -14,6 +16,12 @@ from patchwork.models import State from patchwork.models import UserProfile +class PatchworkTableRenderer(renderers.EngineMixin, renderers.BaseRenderer): + backend = django_template_backend.DjangoTemplates + form_template_name = 'django/forms/table.html' + formset_template_name = 'django/forms/formsets/table.html' + + class RegistrationForm(forms.Form): first_name = forms.CharField(max_length=30, required=False) last_name = forms.CharField(max_length=30, required=False) diff --git patchwork/settings/base.py patchwork/settings/base.py index 045f262f..965c949f 100644 --- patchwork/settings/base.py +++ patchwork/settings/base.py @@ -71,6 +71,8 @@ TEMPLATES = [ }, ] +FORM_RENDERER = 'patchwork.forms.PatchworkTableRenderer' + # TODO(stephenfin): Consider changing to BigAutoField when we drop support for # Django < 3.2 DEFAULT_AUTO_FIELD = 'django.db.models.AutoField' diff --git patchwork/tests/views/test_mail.py patchwork/tests/views/test_mail.py index de9df3d2..ae0b2c38 100644 --- patchwork/tests/views/test_mail.py +++ patchwork/tests/views/test_mail.py @@ -5,6 +5,7 @@ import re +import django from django.core import mail from django.test import TestCase from django.urls import reverse @@ -33,15 +34,37 @@ class MailSettingsTest(TestCase): response = self.client.post(reverse('mail-settings'), {'email': ''}) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'patchwork/mail.html') - self.assertFormError( - response, 'form', 'email', 'This field is required.' - ) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'email', + 'This field is required.', + ) + else: + self.assertFormError( + response, + 'form', + 'email', + 'This field is required.', + ) def test_post_invalid(self): response = self.client.post(reverse('mail-settings'), {'email': 'foo'}) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'patchwork/mail.html') - self.assertFormError(response, 'form', 'email', error_strings['email']) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'email', + error_strings['email'], + ) + else: + self.assertFormError( + response, + 'form', + 'email', + error_strings['email'], + ) def test_post_optin(self): email = 'foo@example.com' @@ -91,9 +114,19 @@ class OptoutRequestTest(TestCase): def test_post_empty(self): response = self.client.post(reverse('mail-optout'), {'email': ''}) self.assertEqual(response.status_code, 200) - self.assertFormError( - response, 'form', 'email', 'This field is required.' - ) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'email', + 'This field is required.', + ) + else: + self.assertFormError( + response, + 'form', + 'email', + 'This field is required.', + ) self.assertTrue(response.context['error']) self.assertNotIn('confirmation', response.context) self.assertEqual(len(mail.outbox), 0) @@ -101,7 +134,19 @@ class OptoutRequestTest(TestCase): def test_post_non_email(self): response = self.client.post(reverse('mail-optout'), {'email': 'foo'}) self.assertEqual(response.status_code, 200) - self.assertFormError(response, 'form', 'email', error_strings['email']) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'email', + error_strings['email'], + ) + else: + self.assertFormError( + response, + 'form', + 'email', + error_strings['email'], + ) self.assertTrue(response.context['error']) self.assertNotIn('confirmation', response.context) self.assertEqual(len(mail.outbox), 0) @@ -172,9 +217,19 @@ class OptinRequestTest(TestCase): def test_post_empty(self): response = self.client.post(reverse('mail-optin'), {'email': ''}) self.assertEqual(response.status_code, 200) - self.assertFormError( - response, 'form', 'email', 'This field is required.' - ) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'email', + 'This field is required.', + ) + else: + self.assertFormError( + response, + 'form', + 'email', + 'This field is required.', + ) self.assertTrue(response.context['error']) self.assertNotIn('confirmation', response.context) self.assertEqual(len(mail.outbox), 0) @@ -182,7 +237,19 @@ class OptinRequestTest(TestCase): def test_post_non_email(self): response = self.client.post(reverse('mail-optin'), {'email': 'foo'}) self.assertEqual(response.status_code, 200) - self.assertFormError(response, 'form', 'email', error_strings['email']) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'email', + error_strings['email'], + ) + else: + self.assertFormError( + response, + 'form', + 'email', + error_strings['email'], + ) self.assertTrue(response.context['error']) self.assertNotIn('confirmation', response.context) self.assertEqual(len(mail.outbox), 0) diff --git patchwork/tests/views/test_patch.py patchwork/tests/views/test_patch.py index 9cea8a0b..d1de8ec9 100644 --- patchwork/tests/views/test_patch.py +++ patchwork/tests/views/test_patch.py @@ -8,6 +8,7 @@ from datetime import timedelta import re import unittest +import django from django.conf import settings from django.test import TestCase from django.urls import reverse @@ -460,13 +461,21 @@ class PatchUpdateTest(TestCase): new_states = [Patch.objects.get(pk=p.pk).state for p in self.patches] self.assertEqual(new_states, orig_states) - self.assertFormError( - response, - 'patchform', - 'state', - 'Select a valid choice. That choice is not one ' - 'of the available choices.', - ) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['patchform'], + 'state', + 'Select a valid choice. That choice is not one ' + 'of the available choices.', + ) + else: + self.assertFormError( + response, + 'patchform', + 'state', + 'Select a valid choice. That choice is not one ' + 'of the available choices.', + ) def _test_delegate_change(self, delegate_str): data = self.base_data.copy() diff --git patchwork/tests/views/test_user.py patchwork/tests/views/test_user.py index 1f74ad50..8ab91670 100644 --- patchwork/tests/views/test_user.py +++ patchwork/tests/views/test_user.py @@ -3,6 +3,7 @@ # # SPDX-License-Identifier: GPL-2.0-or-later +import django from django.contrib.auth.models import User from django.core import mail from django.test.client import Client @@ -70,14 +71,38 @@ class RegistrationTest(TestCase): del data[field] response = self.client.post('/register/', data) self.assertEqual(response.status_code, 200) - self.assertFormError(response, 'form', field, self.required_error) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + field, + self.required_error, + ) + else: + self.assertFormError( + response, + 'form', + field, + self.required_error, + ) def test_invalid_username(self): data = self.default_data.copy() data['username'] = 'invalid user' response = self.client.post('/register/', data) self.assertEqual(response.status_code, 200) - self.assertFormError(response, 'form', 'username', self.invalid_error) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'username', + self.invalid_error, + ) + else: + self.assertFormError( + response, + 'form', + 'username', + self.invalid_error, + ) def test_existing_username(self): user = create_user() @@ -85,12 +110,19 @@ class RegistrationTest(TestCase): data['username'] = user.username response = self.client.post('/register/', data) self.assertEqual(response.status_code, 200) - self.assertFormError( - response, - 'form', - 'username', - 'This username is already taken. Please choose another.', - ) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'username', + 'This username is already taken. Please choose another.', + ) + else: + self.assertFormError( + response, + 'form', + 'username', + 'This username is already taken. Please choose another.', + ) def test_existing_email(self): user = create_user() @@ -98,13 +130,21 @@ class RegistrationTest(TestCase): data['email'] = user.email response = self.client.post('/register/', data) self.assertEqual(response.status_code, 200) - self.assertFormError( - response, - 'form', - 'email', - 'This email address is already in use for the account ' - '"%s".\n' % user.username, - ) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['form'], + 'email', + 'This email address is already in use for the account ' + '"%s".\n' % user.username, + ) + else: + self.assertFormError( + response, + 'form', + 'email', + 'This email address is already in use for the account ' + '"%s".\n' % user.username, + ) def test_valid_registration(self): response = self.client.post('/register/', self.default_data) @@ -255,17 +295,37 @@ class UserLinkTest(_UserTestCase): response = self.client.post(reverse('user-link'), {'email': ''}) self.assertEqual(response.status_code, 200) self.assertTrue(response.context['linkform']) - self.assertFormError( - response, 'linkform', 'email', 'This field is required.' - ) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['linkform'], + 'email', + 'This field is required.', + ) + else: + self.assertFormError( + response, + 'linkform', + 'email', + 'This field is required.', + ) def test_user_person_request_invalid(self): response = self.client.post(reverse('user-link'), {'email': 'foo'}) self.assertEqual(response.status_code, 200) self.assertTrue(response.context['linkform']) - self.assertFormError( - response, 'linkform', 'email', error_strings['email'] - ) + if django.VERSION >= (4, 1): + self.assertFormError( + response.context['linkform'], + 'email', + error_strings['email'], + ) + else: + self.assertFormError( + response, + 'linkform', + 'email', + error_strings['email'], + ) def test_user_person_request_valid(self): response = self.client.post( diff --git releasenotes/notes/django-4-1-support-bcbe65a71d235b43.yaml releasenotes/notes/django-4-1-support-bcbe65a71d235b43.yaml new file mode 100644 index 00000000..3dcab1bd --- /dev/null +++ releasenotes/notes/django-4-1-support-bcbe65a71d235b43.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + `Django 4.1 `_ is + now supported. diff --git tox.ini tox.ini index aa434a72..f243faca 100644 --- tox.ini +++ tox.ini @@ -1,6 +1,6 @@ [tox] minversion = 3.2 -envlist = pep8,docs,py{37,38,39}-django32,py{38,39,310}-django{40} +envlist = pep8,docs,py{37,38,39}-django32,py{38,39,310}-django{40,41} skipsdist = true ignore_basepython_conflict = true @@ -9,11 +9,14 @@ basepython = python3 deps = -r{toxinidir}/requirements-test.txt django32: django~=3.2.0 - django32: djangorestframework~=3.13.0 + django32: djangorestframework~=3.14.0 django32: django-filter~=22.1.0 django40: django~=4.0.0 - django40: djangorestframework~=3.13.0 + django40: djangorestframework~=3.14.0 django40: django-filter~=22.1.0 + django41: django~=4.1.0 + django41: djangorestframework~=3.14.0 + django41: django-filter~=22.1.0 setenv = DJANGO_SETTINGS_MODULE = patchwork.settings.dev PYTHONDONTWRITEBYTECODE = 1 From patchwork Fri Sep 30 16:19:16 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Finucane X-Patchwork-Id: 1684921 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.ozlabs.org (client-ip=2404:9400:2:0:216:3eff:fee1:b9f1; helo=lists.ozlabs.org; envelope-from=patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=Gv3dSll3; dkim-atps=neutral Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2404:9400:2:0:216:3eff:fee1:b9f1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4MfFn914lqz1yql for ; Sat, 1 Oct 2022 02:19:57 +1000 (AEST) Received: from boromir.ozlabs.org (localhost [IPv6:::1]) by lists.ozlabs.org (Postfix) with ESMTP id 4MfFn90WNBz3c6w for ; Sat, 1 Oct 2022 02:19:57 +1000 (AEST) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=Gv3dSll3; dkim-atps=neutral X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=none (no SPF record) smtp.mailfrom=that.guru (client-ip=136.175.108.92; helo=mail-108-mta92.mxroute.com; envelope-from=stephen@that.guru; receiver=) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=Gv3dSll3; dkim-atps=neutral Received: from mail-108-mta92.mxroute.com (mail-108-mta92.mxroute.com [136.175.108.92]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 4MfFmn6mbVz3c6Q for ; Sat, 1 Oct 2022 02:19:36 +1000 (AEST) Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta92.mxroute.com (ZoneMTA) with ESMTPSA id 1838f321c040002b7a.001 for (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Fri, 30 Sep 2022 16:19:29 +0000 X-Zone-Loop: 578b811d774a0bb7cbb3983bcd230adcdaeeb226e802 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=that.guru; s=x; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=CEJ3VeHGOjiT0dJQhteFnSJ9tyt1M/ywCAaYK+cxW4k=; b=Gv3dSll3uIRWCmGmXd9h/afI4b p8o1bMUO0TySWHRIlT2j/Qx5z+6qk4n/MvBRTKSOX1wWVi1owDEphbpBtCAG0VuwHtgol9UjXJS/m T4m1o+NxMlZ8hstfVFYZtUEpTzm2JxjICFi5cZQxP05vgqdSQN3QsDCavbh1+C9LYkqJ7K9d2d57B QGAQGEYR41Km1jeoVmF5gF6DtLbBjPdzApx/t77T5qiuZxa9HlYnO+G19NV2l6aXkJaSDC+HKN0on 5KabiXWMd4Bcxo7iEZeshT6J/wpBKuP84t3UZTCPs/++MTm+gpB+Mza0LbzPBfw9rYy+o/213txuQ puHp6rpA==; From: Stephen Finucane To: patchwork@lists.ozlabs.org Subject: [PATCH 05/10] manage: Check Django version on startup Date: Fri, 30 Sep 2022 17:19:16 +0100 Message-Id: <20220930161921.266633-5-stephen@that.guru> In-Reply-To: <20220930161921.266633-1-stephen@that.guru> References: <20220930161921.266633-1-stephen@that.guru> MIME-Version: 1.0 X-Authenticated-Id: stephen@that.guru X-BeenThere: patchwork@lists.ozlabs.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Patchwork development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Carlos O'Donell , Siddhesh Poyarekar , DJ Delorie Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" This was recently reported as an issue. Add a simple check to ensure people update their dependencies as expected. Signed-off-by: Stephen Finucane Cc: Siddhesh Poyarekar Cc: DJ Delorie Cc: Carlos O'Donell --- manage.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git manage.py manage.py index 033c8ae4..e227481e 100755 --- manage.py +++ manage.py @@ -7,6 +7,11 @@ if __name__ == "__main__": "DJANGO_SETTINGS_MODULE", "patchwork.settings.production" ) + import django + + if django.VERSION < (3, 2): + raise Exception('patchwork requires Django 3.2 or greater') + from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) From patchwork Fri Sep 30 16:19:17 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Finucane X-Patchwork-Id: 1684925 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.ozlabs.org (client-ip=112.213.38.117; helo=lists.ozlabs.org; envelope-from=patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=oIldzOgG; dkim-atps=neutral Received: from lists.ozlabs.org (lists.ozlabs.org [112.213.38.117]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4MfFng6kyBz1ypH for ; Sat, 1 Oct 2022 02:20:23 +1000 (AEST) Received: from boromir.ozlabs.org (localhost [IPv6:::1]) by lists.ozlabs.org (Postfix) with ESMTP id 4MfFng6Z44z3c6l for ; Sat, 1 Oct 2022 02:20:23 +1000 (AEST) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=oIldzOgG; dkim-atps=neutral X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=none (no SPF record) smtp.mailfrom=that.guru (client-ip=136.175.108.156; helo=mail-108-mta156.mxroute.com; envelope-from=stephen@that.guru; receiver=) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=oIldzOgG; dkim-atps=neutral Received: from mail-108-mta156.mxroute.com (mail-108-mta156.mxroute.com [136.175.108.156]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 4MfFmy3s3rz3c8h for ; Sat, 1 Oct 2022 02:19:45 +1000 (AEST) Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta156.mxroute.com (ZoneMTA) with ESMTPSA id 1838f3220550002b7a.001 for (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Fri, 30 Sep 2022 16:19:30 +0000 X-Zone-Loop: 4047a2d7ca562b85bd9d5d043ad84c6c3b3854bbd4d5 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=that.guru; s=x; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=zab8lzcGTRqlWNdXHUwwd2AKiCD4Vr8cYoS5HiCfsOQ=; b=oIldzOgGDRpxKjSboKizxdAzpy xKLk5TBgnlHx4Bf3y6JpTA0HprmJ8C0fq/qpHHYDBJArE1Fok8SzszYCMT1aWTIu8xlUk85Qg8HCL qY7bWoTUa3A6Sg/VRn7Fy844lKIQQcTxMpySGHRTlIR4+SZqh7B+FvwJNorl/v0ICgH++2FYIqNCR At8rAnktfuW4VaWecUuRHC960ke++dBSD0/gIYKe8bS2tgqmF2W/ZGi5tWhBLedPKNegoNtPH2/S4 c3XV+fPwvZENuVQnytyXSGHRNqgE+ASUtCXqJp1v4jovZVU7DOt/jFDCPgyupUl564hpYuVTcY1ub jlsU7JOw==; From: Stephen Finucane To: patchwork@lists.ozlabs.org Subject: [PATCH 06/10] REST: Fix issues with comment-related events Date: Fri, 30 Sep 2022 17:19:17 +0100 Message-Id: <20220930161921.266633-6-stephen@that.guru> In-Reply-To: <20220930161921.266633-1-stephen@that.guru> References: <20220930161921.266633-1-stephen@that.guru> MIME-Version: 1.0 X-Authenticated-Id: stephen@that.guru X-BeenThere: patchwork@lists.ozlabs.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Patchwork development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Carlos O'Donell , Siddhesh Poyarekar , DJ Delorie Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" When we introduced this functionality, we missed the fact that these resources use nested-style URLs that need to be specially handled. Fix this now. Signed-off-by: Stephen Finucane Fixes: e3d8f7548 ("REST: Add 'patch-comment-created', 'cover-comment-created' events") Cc: Siddhesh Poyarekar Cc: DJ Delorie Cc: Carlos O'Donell --- patchwork/api/base.py | 34 +++++++++++++++++++++++++++++++ patchwork/api/embedded.py | 10 +++++++-- patchwork/api/event.py | 25 ++++++++++++++++++++--- patchwork/tests/api/test_event.py | 30 +++++++++++++++++---------- 4 files changed, 83 insertions(+), 16 deletions(-) diff --git patchwork/api/base.py patchwork/api/base.py index 6268f67d..3ed4182c 100644 --- patchwork/api/base.py +++ patchwork/api/base.py @@ -139,6 +139,40 @@ class CheckHyperlinkedIdentityField(HyperlinkedIdentityField): ) +class CoverCommentHyperlinkedIdentityField(HyperlinkedIdentityField): + def get_url(self, obj, view_name, request, format): + # Unsaved objects will not yet have a valid URL. + if obj.pk is None: + return None + + return self.reverse( + view_name, + kwargs={ + 'cover_id': obj.cover.id, + 'comment_id': obj.id, + }, + request=request, + format=format, + ) + + +class PatchCommentHyperlinkedIdentityField(HyperlinkedIdentityField): + def get_url(self, obj, view_name, request, format): + # Unsaved objects will not yet have a valid URL. + if obj.pk is None: + return None + + return self.reverse( + view_name, + kwargs={ + 'patch_id': obj.patch.id, + 'comment_id': obj.id, + }, + request=request, + format=format, + ) + + class BaseHyperlinkedModelSerializer(HyperlinkedModelSerializer): def to_representation(self, instance): data = super(BaseHyperlinkedModelSerializer, self).to_representation( diff --git patchwork/api/embedded.py patchwork/api/embedded.py index c41511fe..485ed6f7 100644 --- patchwork/api/embedded.py +++ patchwork/api/embedded.py @@ -17,6 +17,8 @@ from rest_framework.serializers import SerializerMethodField from patchwork.api.base import BaseHyperlinkedModelSerializer from patchwork.api.base import CheckHyperlinkedIdentityField +from patchwork.api.base import CoverCommentHyperlinkedIdentityField +from patchwork.api.base import PatchCommentHyperlinkedIdentityField from patchwork import models @@ -127,6 +129,9 @@ class CoverSerializer(SerializedRelatedField): class CoverCommentSerializer(SerializedRelatedField): class _Serializer(MboxMixin, WebURLMixin, BaseHyperlinkedModelSerializer): + + url = CoverCommentHyperlinkedIdentityField('api-cover-comment-detail') + class Meta: model = models.CoverComment fields = ( @@ -136,7 +141,6 @@ class CoverCommentSerializer(SerializedRelatedField): 'msgid', 'list_archive_url', 'date', - 'name', ) read_only_fields = fields versioned_fields = { @@ -177,6 +181,9 @@ class PatchSerializer(SerializedRelatedField): class PatchCommentSerializer(SerializedRelatedField): class _Serializer(MboxMixin, WebURLMixin, BaseHyperlinkedModelSerializer): + + url = PatchCommentHyperlinkedIdentityField('api-patch-comment-detail') + class Meta: model = models.PatchComment fields = ( @@ -186,7 +193,6 @@ class PatchCommentSerializer(SerializedRelatedField): 'msgid', 'list_archive_url', 'date', - 'name', ) read_only_fields = fields versioned_fields = { diff --git patchwork/api/event.py patchwork/api/event.py index c1b09ab9..6d08b6ee 100644 --- patchwork/api/event.py +++ patchwork/api/event.py @@ -19,6 +19,7 @@ from patchwork.api.embedded import ProjectSerializer from patchwork.api.embedded import SeriesSerializer from patchwork.api.embedded import UserSerializer from patchwork.api.filters import EventFilterSet +from patchwork.api import utils from patchwork.models import Event @@ -140,7 +141,7 @@ class EventList(ListAPIView): ordering = '-date' def get_queryset(self): - return Event.objects.all().prefetch_related( + events = Event.objects.all().prefetch_related( 'project', 'patch__project', 'series__project', @@ -150,6 +151,24 @@ class EventList(ListAPIView): 'previous_delegate', 'current_delegate', 'created_check', - 'cover_comment', - 'patch_comment', ) + # NOTE(stephenfin): We need to exclude comment-related events because + # until API v1.3, we didn't have an comment detail API to point to. + # This goes against our pledge to version events in the docs but must + # be done. + # TODO(stephenfin): Make this more generic. + if utils.has_version(self.request, '1.3'): + events = events.prefetch_related( + 'cover_comment', + 'cover_comment__cover__project', + 'patch_comment', + 'patch_comment__patch__project', + ) + else: + events = events.exclude( + category__in=[ + Event.CATEGORY_COVER_COMMENT_CREATED, + Event.CATEGORY_PATCH_COMMENT_CREATED, + ] + ) + return events diff --git patchwork/tests/api/test_event.py patchwork/tests/api/test_event.py index 9708f96b..7ca09c2e 100644 --- patchwork/tests/api/test_event.py +++ patchwork/tests/api/test_event.py @@ -12,8 +12,10 @@ from patchwork.models import Event from patchwork.tests.api import utils from patchwork.tests.utils import create_check from patchwork.tests.utils import create_cover +from patchwork.tests.utils import create_cover_comment from patchwork.tests.utils import create_maintainer from patchwork.tests.utils import create_patch +from patchwork.tests.utils import create_patch_comment from patchwork.tests.utils import create_series from patchwork.tests.utils import create_state @@ -70,7 +72,7 @@ class TestEventAPI(APITestCase): # patch-created, patch-completed, series-completed patch = create_patch(series=series) # cover-created - create_cover(series=series) + cover = create_cover(series=series) # check-created create_check(patch=patch) # patch-delegated, patch-state-changed @@ -81,6 +83,9 @@ class TestEventAPI(APITestCase): patch.state = state self.assertTrue(patch.is_editable(actor)) patch.save() + # patch-cover-created, cover-comment-created + create_patch_comment(patch=patch, submitter=patch.submitter) + create_cover_comment(cover=cover, submitter=cover.submitter) return Event.objects.all() @@ -91,7 +96,9 @@ class TestEventAPI(APITestCase): resp = self.client.get(self.api_url()) self.assertEqual(status.HTTP_200_OK, resp.status_code) - self.assertEqual(8, len(resp.data), [x['category'] for x in resp.data]) + self.assertEqual( + 10, len(resp.data), [x['category'] for x in resp.data] + ) for event_rsp in resp.data: event_obj = events.get(category=event_rsp['category']) self.assertSerialized(event_obj, event_rsp) @@ -104,7 +111,7 @@ class TestEventAPI(APITestCase): resp = self.client.get(self.api_url(), {'project': project.pk}) # All but one event belongs to the same project - self.assertEqual(8, len(resp.data)) + self.assertEqual(10, len(resp.data)) resp = self.client.get(self.api_url(), {'project': 'invalidproject'}) self.assertEqual(0, len(resp.data)) @@ -132,9 +139,9 @@ class TestEventAPI(APITestCase): patch = events.get(category='patch-created').patch resp = self.client.get(self.api_url(), {'patch': patch.pk}) - # There should be five - patch-created, patch-completed, check-created, - # patch-state-changed and patch-delegated - self.assertEqual(5, len(resp.data)) + # There should be six - patch-created, patch-completed, check-created, + # patch-state-changed, patch-delegated and patch-comment-created + self.assertEqual(6, len(resp.data)) resp = self.client.get(self.api_url(), {'patch': 999999}) self.assertEqual(0, len(resp.data)) @@ -145,8 +152,8 @@ class TestEventAPI(APITestCase): cover = events.get(category='cover-created').cover resp = self.client.get(self.api_url(), {'cover': cover.pk}) - # There should only be one - cover-created - self.assertEqual(1, len(resp.data)) + # There should be two - cover-created and cover-comment-created + self.assertEqual(2, len(resp.data)) resp = self.client.get(self.api_url(), {'cover': 999999}) self.assertEqual(0, len(resp.data)) @@ -170,7 +177,7 @@ class TestEventAPI(APITestCase): # The final two events (patch-delegated, patch-state-changed) # have an actor set - actor = events[0].actor + actor = events.get(category='patch-delegated').actor resp = self.client.get(self.api_url(), {'actor': actor.pk}) self.assertEqual(2, len(resp.data)) @@ -185,14 +192,15 @@ class TestEventAPI(APITestCase): resp = self.client.get( self.api_url(version='1.1'), {'actor': 'foo-bar'} ) - self.assertEqual(len(events), len(resp.data)) + # we don't see the two comment-related fields + self.assertEqual(len(events) - 2, len(resp.data)) def test_list_bug_335(self): """Ensure we retrieve the embedded series project once.""" for _ in range(3): self._create_events() - with self.assertNumQueries(27): + with self.assertNumQueries(33): self.client.get(self.api_url()) def test_order_by_date_default(self): From patchwork Fri Sep 30 16:19:18 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Finucane X-Patchwork-Id: 1684923 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.ozlabs.org (client-ip=2404:9400:2:0:216:3eff:fee1:b9f1; helo=lists.ozlabs.org; envelope-from=patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=qv0Nu8bA; dkim-atps=neutral Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2404:9400:2:0:216:3eff:fee1:b9f1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4MfFnQ0pXXz1ypH for ; Sat, 1 Oct 2022 02:20:10 +1000 (AEST) Received: from boromir.ozlabs.org (localhost [IPv6:::1]) by lists.ozlabs.org (Postfix) with ESMTP id 4MfFnQ0byqz3c7S for ; Sat, 1 Oct 2022 02:20:10 +1000 (AEST) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=qv0Nu8bA; dkim-atps=neutral X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=none (no SPF record) smtp.mailfrom=that.guru (client-ip=136.175.108.112; helo=mail-108-mta112.mxroute.com; envelope-from=stephen@that.guru; receiver=) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=qv0Nu8bA; dkim-atps=neutral Received: from mail-108-mta112.mxroute.com (mail-108-mta112.mxroute.com [136.175.108.112]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 4MfFmx5yb9z3cCY for ; Sat, 1 Oct 2022 02:19:45 +1000 (AEST) Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta112.mxroute.com (ZoneMTA) with ESMTPSA id 1838f3222150002b7a.001 for (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Fri, 30 Sep 2022 16:19:30 +0000 X-Zone-Loop: 67c0264af5b244b8b605f8d0dbccb54ec27c0ea75f22 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=that.guru; s=x; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=NndQUqRVXJDGoxQrP4gwVEvjq8WuPXk5cPQlI0rz/8s=; b=qv0Nu8bAqIXEuoueU+wYZLlrvP hzlknEL7OBk2yA7e6+l+gSeOvGtKl5cQEI5lwkKEnKdj6RI/hDMz9K8z6moVtY1PDI8WK+Ytx9mKA FgGxUjD83qWfL0lcJ7MNwdUrhBIJr5iOSL0Kt6dQ66faf9VRKPfcKy9wF4+j/c2Wx/x81mRP48H02 AIcGbDdoG2BEw9Pable/wp14+vVgJjVvtG3RVyix8W4v0uNdtKRXk2adzp7CEPo9y0TSkDWKBl3yx mIKUn1e2TxJ7ZB70EHPeo0BhMkZpcPpiaBjbsptQA9QHja12d9vJpk8BqE/xjCtNebVFYWL5ZG1IB 9g0xDFVA==; From: Stephen Finucane To: patchwork@lists.ozlabs.org Subject: [PATCH 07/10] REST: De-duplicate handling of nested resource URLs Date: Fri, 30 Sep 2022 17:19:18 +0100 Message-Id: <20220930161921.266633-7-stephen@that.guru> In-Reply-To: <20220930161921.266633-1-stephen@that.guru> References: <20220930161921.266633-1-stephen@that.guru> MIME-Version: 1.0 X-Authenticated-Id: stephen@that.guru X-BeenThere: patchwork@lists.ozlabs.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Patchwork development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" These were all doing the same thing. Make things more generic. We also speed up test (inadvertently) by using the 'patch_id' attribute of the 'Check' model rather than 'patch.id', thus avoiding the JOIN. Signed-off-by: Stephen Finucane --- patchwork/api/base.py | 52 +++++++++---------------------- patchwork/api/check.py | 10 ++++-- patchwork/api/embedded.py | 28 +++++++++++++---- patchwork/tests/api/test_event.py | 2 +- 4 files changed, 45 insertions(+), 47 deletions(-) diff --git patchwork/api/base.py patchwork/api/base.py index 3ed4182c..0f5c44a2 100644 --- patchwork/api/base.py +++ patchwork/api/base.py @@ -9,8 +9,8 @@ from django.conf import settings from django.shortcuts import get_object_or_404 from rest_framework import permissions from rest_framework.pagination import PageNumberPagination +from rest_framework.relations import HyperlinkedIdentityField from rest_framework.response import Response -from rest_framework.serializers import HyperlinkedIdentityField from rest_framework.serializers import HyperlinkedModelSerializer from rest_framework.utils.urls import replace_query_param @@ -122,52 +122,28 @@ class MultipleFieldLookupMixin(object): return get_object_or_404(queryset, **filter_kwargs) -class CheckHyperlinkedIdentityField(HyperlinkedIdentityField): - def get_url(self, obj, view_name, request, format): - # Unsaved objects will not yet have a valid URL. - if obj.pk is None: - return None - - return self.reverse( - view_name, - kwargs={ - 'patch_id': obj.patch.id, - 'check_id': obj.id, - }, - request=request, - format=format, - ) +class NestedHyperlinkedIdentityField(HyperlinkedIdentityField): + """A variant of HyperlinkedIdentityField that supports nested resources.""" + def __init__(self, view_name, lookup_field_mapping, **kwargs): + self.lookup_field_mapping = lookup_field_mapping + super().__init__(view_name, **kwargs) -class CoverCommentHyperlinkedIdentityField(HyperlinkedIdentityField): def get_url(self, obj, view_name, request, format): # Unsaved objects will not yet have a valid URL. - if obj.pk is None: + if hasattr(obj, 'pk') and obj.pk in (None, ''): return None - return self.reverse( - view_name, - kwargs={ - 'cover_id': obj.cover.id, - 'comment_id': obj.id, - }, - request=request, - format=format, - ) - - -class PatchCommentHyperlinkedIdentityField(HyperlinkedIdentityField): - def get_url(self, obj, view_name, request, format): - # Unsaved objects will not yet have a valid URL. - if obj.pk is None: - return None + kwargs = {} + for ( + lookup_url_kwarg, + lookup_field, + ) in self.lookup_field_mapping.items(): + kwargs[lookup_url_kwarg] = getattr(obj, lookup_field) return self.reverse( view_name, - kwargs={ - 'patch_id': obj.patch.id, - 'comment_id': obj.id, - }, + kwargs=kwargs, request=request, format=format, ) diff --git patchwork/api/check.py patchwork/api/check.py index c28d89f7..f5461fc6 100644 --- patchwork/api/check.py +++ patchwork/api/check.py @@ -14,8 +14,8 @@ from rest_framework.serializers import HiddenField from rest_framework.serializers import HyperlinkedModelSerializer from rest_framework.serializers import ValidationError -from patchwork.api.base import CheckHyperlinkedIdentityField from patchwork.api.base import MultipleFieldLookupMixin +from patchwork.api.base import NestedHyperlinkedIdentityField from patchwork.api.base import CurrentPatchDefault from patchwork.api.embedded import UserSerializer from patchwork.api.filters import CheckFilterSet @@ -25,7 +25,13 @@ from patchwork.models import Patch class CheckSerializer(HyperlinkedModelSerializer): - url = CheckHyperlinkedIdentityField('api-check-detail') + url = NestedHyperlinkedIdentityField( + 'api-check-detail', + lookup_field_mapping={ + 'patch_id': 'patch_id', + 'check_id': 'id', + }, + ) patch = HiddenField(default=CurrentPatchDefault()) user = UserSerializer(default=CurrentUserDefault()) diff --git patchwork/api/embedded.py patchwork/api/embedded.py index 485ed6f7..7105da08 100644 --- patchwork/api/embedded.py +++ patchwork/api/embedded.py @@ -16,9 +16,7 @@ from rest_framework.serializers import PrimaryKeyRelatedField from rest_framework.serializers import SerializerMethodField from patchwork.api.base import BaseHyperlinkedModelSerializer -from patchwork.api.base import CheckHyperlinkedIdentityField -from patchwork.api.base import CoverCommentHyperlinkedIdentityField -from patchwork.api.base import PatchCommentHyperlinkedIdentityField +from patchwork.api.base import NestedHyperlinkedIdentityField from patchwork import models @@ -82,7 +80,13 @@ class WebURLMixin(BaseHyperlinkedModelSerializer): class CheckSerializer(SerializedRelatedField): class _Serializer(BaseHyperlinkedModelSerializer): - url = CheckHyperlinkedIdentityField('api-check-detail') + url = NestedHyperlinkedIdentityField( + 'api-check-detail', + lookup_field_mapping={ + 'patch_id': 'patch_id', + 'check_id': 'id', + }, + ) def to_representation(self, instance): data = super(CheckSerializer._Serializer, self).to_representation( @@ -130,7 +134,13 @@ class CoverSerializer(SerializedRelatedField): class CoverCommentSerializer(SerializedRelatedField): class _Serializer(MboxMixin, WebURLMixin, BaseHyperlinkedModelSerializer): - url = CoverCommentHyperlinkedIdentityField('api-cover-comment-detail') + url = NestedHyperlinkedIdentityField( + 'api-cover-comment-detail', + lookup_field_mapping={ + 'cover_id': 'cover_id', + 'comment_id': 'id', + }, + ) class Meta: model = models.CoverComment @@ -182,7 +192,13 @@ class PatchSerializer(SerializedRelatedField): class PatchCommentSerializer(SerializedRelatedField): class _Serializer(MboxMixin, WebURLMixin, BaseHyperlinkedModelSerializer): - url = PatchCommentHyperlinkedIdentityField('api-patch-comment-detail') + url = NestedHyperlinkedIdentityField( + 'api-patch-comment-detail', + lookup_field_mapping={ + 'patch_id': 'patch_id', + 'comment_id': 'id', + }, + ) class Meta: model = models.PatchComment diff --git patchwork/tests/api/test_event.py patchwork/tests/api/test_event.py index 7ca09c2e..1a0d811d 100644 --- patchwork/tests/api/test_event.py +++ patchwork/tests/api/test_event.py @@ -200,7 +200,7 @@ class TestEventAPI(APITestCase): for _ in range(3): self._create_events() - with self.assertNumQueries(33): + with self.assertNumQueries(30): self.client.get(self.api_url()) def test_order_by_date_default(self): From patchwork Fri Sep 30 16:19:19 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Finucane X-Patchwork-Id: 1684924 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.ozlabs.org (client-ip=112.213.38.117; helo=lists.ozlabs.org; envelope-from=patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=PZQNTs46; dkim-atps=neutral Received: from lists.ozlabs.org (lists.ozlabs.org [112.213.38.117]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4MfFnY0cGKz1ypH for ; Sat, 1 Oct 2022 02:20:17 +1000 (AEST) Received: from boromir.ozlabs.org (localhost [IPv6:::1]) by lists.ozlabs.org (Postfix) with ESMTP id 4MfFnY05Hwz3c7P for ; Sat, 1 Oct 2022 02:20:17 +1000 (AEST) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=PZQNTs46; dkim-atps=neutral X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=none (no SPF record) smtp.mailfrom=that.guru (client-ip=136.175.108.55; helo=mail-108-mta55.mxroute.com; envelope-from=stephen@that.guru; receiver=) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=PZQNTs46; dkim-atps=neutral Received: from mail-108-mta55.mxroute.com (mail-108-mta55.mxroute.com [136.175.108.55]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 4MfFmy3J3Tz3cDs for ; Sat, 1 Oct 2022 02:19:45 +1000 (AEST) Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta55.mxroute.com (ZoneMTA) with ESMTPSA id 1838f3224120002b7a.001 for (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Fri, 30 Sep 2022 16:19:31 +0000 X-Zone-Loop: b1c32e2e223aaaf51f060f3e067e445bafd7c292bfed X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=that.guru; s=x; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=pjsuISrdfm57LuXQ5wCocKeSqNHpyORFvPByrkVNdwQ=; b=PZQNTs46Ktsx5dBRVGyEfvJ3Vf zEU0kI6PfoOqz8txOpsbVZg/Xn4NYMILC4Z7CcP90DEMDzGy/xl/5D+cfFfUappYz5FHgBgF9eVR+ TM9I490dRW+z4plma4e+exmEDQ/a1n7pvTpO3wuiYbKFnisVQBwnz+3d0upi71ySvnzSGC1kSE3xn CISf2bRAzXzsAkxrxYjDcvxgmlA/LgAESn4+xv7bQ//dvR9yK9j8x37e8bugcKHYv6t8amE4XF5AS aSUfN7SJBDMTGDJuArtn225mCcp3ELtgVBZDtzlNqpvx9XH0VpDX5+mXIAaduGVGclvA0hHoNPai5 bGymj8BQ==; From: Stephen Finucane To: patchwork@lists.ozlabs.org Subject: [PATCH 08/10] models: Cache 'list_archive_url' property Date: Fri, 30 Sep 2022 17:19:19 +0100 Message-Id: <20220930161921.266633-8-stephen@that.guru> In-Reply-To: <20220930161921.266633-1-stephen@that.guru> References: <20220930161921.266633-1-stephen@that.guru> MIME-Version: 1.0 X-Authenticated-Id: stephen@that.guru X-BeenThere: patchwork@lists.ozlabs.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Patchwork development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" We really need to get rid of this from the embedded view. It's way too slow. For now, we just cache it and leave a note for future us. Signed-off-by: Stephen Finucane --- patchwork/api/embedded.py | 12 ++++++++++++ patchwork/models.py | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git patchwork/api/embedded.py patchwork/api/embedded.py index 7105da08..4cfdf8e6 100644 --- patchwork/api/embedded.py +++ patchwork/api/embedded.py @@ -113,6 +113,8 @@ class CoverSerializer(SerializedRelatedField): 'url', 'web_url', 'msgid', + # TODO(stephenfin): Drop this in a future API version - it is + # too slow to calculate and not necessary here. 'list_archive_url', 'date', 'name', @@ -149,6 +151,8 @@ class CoverCommentSerializer(SerializedRelatedField): 'url', 'web_url', 'msgid', + # TODO(stephenfin): Drop this in a future API version - it is + # too slow to calculate and not necessary here. 'list_archive_url', 'date', ) @@ -174,6 +178,8 @@ class PatchSerializer(SerializedRelatedField): 'url', 'web_url', 'msgid', + # TODO(stephenfin): Drop this in a future API version - it is + # too slow to calculate and not necessary here. 'list_archive_url', 'date', 'name', @@ -207,6 +213,8 @@ class PatchCommentSerializer(SerializedRelatedField): 'url', 'web_url', 'msgid', + # TODO(stephenfin): Drop this in a future API version - it is + # too slow to calculate and not necessary here. 'list_archive_url', 'date', ) @@ -253,8 +261,12 @@ class ProjectSerializer(SerializedRelatedField): 'web_url', 'scm_url', 'webscm_url', + # TODO(stephenfin): Drop this in a future API version - it is + # too slow to calculate and not necessary here. 'list_archive_url', + # TODO(stephenfin): Ditto 'list_archive_url_format', + # TODO(stephenfin): Ditto 'commit_url_format', ) read_only_fields = fields diff --git patchwork/models.py patchwork/models.py index 264af532..d2507d4f 100644 --- patchwork/models.py +++ patchwork/models.py @@ -406,7 +406,7 @@ class SubmissionMixin(FilenameMixin, EmailMixin, models.Model): name = models.CharField(max_length=255) - @property + @cached_property def list_archive_url(self): if not self.project.list_archive_url_format: return None @@ -719,7 +719,7 @@ class CoverComment(EmailMixin, models.Model): ) addressed = models.BooleanField(null=True) - @property + @cached_property def list_archive_url(self): if not self.cover.project.list_archive_url_format: return None @@ -770,7 +770,7 @@ class PatchComment(EmailMixin, models.Model): ) addressed = models.BooleanField(null=True) - @property + @cached_property def list_archive_url(self): if not self.patch.project.list_archive_url_format: return None From patchwork Fri Sep 30 16:19:20 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Finucane X-Patchwork-Id: 1684926 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.ozlabs.org (client-ip=2404:9400:2:0:216:3eff:fee1:b9f1; helo=lists.ozlabs.org; envelope-from=patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=MrsQN67y; dkim-atps=neutral Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2404:9400:2:0:216:3eff:fee1:b9f1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4MfFnp5kRgz1ypH for ; Sat, 1 Oct 2022 02:20:30 +1000 (AEST) Received: from boromir.ozlabs.org (localhost [IPv6:::1]) by lists.ozlabs.org (Postfix) with ESMTP id 4MfFnp5Wv7z2xJ6 for ; Sat, 1 Oct 2022 02:20:30 +1000 (AEST) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=MrsQN67y; dkim-atps=neutral X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=none (no SPF record) smtp.mailfrom=that.guru (client-ip=136.175.108.92; helo=mail-108-mta92.mxroute.com; envelope-from=stephen@that.guru; receiver=) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=MrsQN67y; dkim-atps=neutral Received: from mail-108-mta92.mxroute.com (mail-108-mta92.mxroute.com [136.175.108.92]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 4MfFmy48SDz3cF8 for ; Sat, 1 Oct 2022 02:19:45 +1000 (AEST) Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta92.mxroute.com (ZoneMTA) with ESMTPSA id 1838f3226ef0002b7a.001 for (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Fri, 30 Sep 2022 16:19:32 +0000 X-Zone-Loop: dcb1872e6596d1c47010638054926d08762ea60732b0 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=that.guru; s=x; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=hS7KrCY/ySCcIhEJd34Xa4heaLV3ac7efQN8k0iyA3s=; b=MrsQN67y+WG9VNS8/LVD4+fzgd XLWXl1/lnQd9n97Ortw6G14tVwMECE/VX55HEL4r5IWbN9IOlNtZDB8Yb/qwhaaDwuMRPwrdffZcW Cp3vxei8ZVxQV892SHIGY2ZnpdrsjCbOrkIWrtpwfjLCf7+BlYOvs7GqigGQGEdBBOnKx/2FQBX4k FB5M2Iuxn6f2nGGmyExfZFR9a0JQfWqWMFtK73X2wPQD5vvKV+D73Ets+Vd+ssXJNUhBM/GM3sc92 qCuiu1tDq3E9DaBgbgf8VD70Df3Boy7ONoCgrxLPn+AnI7yVXzBrLRRpwl6uQuIqNc50qGs7hN910 gZ+iplGA==; From: Stephen Finucane To: patchwork@lists.ozlabs.org Subject: [PATCH 09/10] REST: Add missing 'url' parameter for comments Date: Fri, 30 Sep 2022 17:19:20 +0100 Message-Id: <20220930161921.266633-9-stephen@that.guru> In-Reply-To: <20220930161921.266633-1-stephen@that.guru> References: <20220930161921.266633-1-stephen@that.guru> MIME-Version: 1.0 X-Authenticated-Id: stephen@that.guru X-BeenThere: patchwork@lists.ozlabs.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Patchwork development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" This should be present on all resources. Signed-off-by: Stephen Finucane Fixes: 88f56051 ("api: add comments detail endpoint") --- docs/api/schemas/latest/patchwork.yaml | 5 +++++ docs/api/schemas/patchwork.j2 | 9 +++++++++ docs/api/schemas/v1.0/patchwork.yaml | 5 ----- docs/api/schemas/v1.1/patchwork.yaml | 5 ----- docs/api/schemas/v1.2/patchwork.yaml | 5 ----- docs/api/schemas/v1.3/patchwork.yaml | 5 +++++ patchwork/api/base.py | 14 ++++++-------- patchwork/api/bundle.py | 2 +- patchwork/api/comment.py | 24 ++++++++++++++++++++++-- patchwork/api/embedded.py | 2 ++ patchwork/api/patch.py | 1 + 11 files changed, 51 insertions(+), 26 deletions(-) diff --git docs/api/schemas/latest/patchwork.yaml docs/api/schemas/latest/patchwork.yaml index 3a1fdd3a..b3de0db5 100644 --- docs/api/schemas/latest/patchwork.yaml +++ docs/api/schemas/latest/patchwork.yaml @@ -1627,6 +1627,11 @@ components: title: ID type: integer readOnly: true + url: + title: URL + type: string + format: uri + readOnly: true web_url: title: Web URL type: string diff --git docs/api/schemas/patchwork.j2 docs/api/schemas/patchwork.j2 index b9786654..68655348 100644 --- docs/api/schemas/patchwork.j2 +++ docs/api/schemas/patchwork.j2 @@ -1683,6 +1683,13 @@ components: title: ID type: integer readOnly: true +{% if version >= (1, 3) %} + url: + title: URL + type: string + format: uri + readOnly: true +{% endif %} {% if version >= (1, 1) %} web_url: title: Web URL @@ -2528,11 +2535,13 @@ components: title: ID type: integer readOnly: true +{% if version >= (1, 3) %} url: title: URL type: string format: uri readOnly: true +{% endif %} {% if version >= (1, 1) %} web_url: title: Web URL diff --git docs/api/schemas/v1.0/patchwork.yaml docs/api/schemas/v1.0/patchwork.yaml index 817b2f2a..6c3893ec 100644 --- docs/api/schemas/v1.0/patchwork.yaml +++ docs/api/schemas/v1.0/patchwork.yaml @@ -1993,11 +1993,6 @@ components: title: ID type: integer readOnly: true - url: - title: URL - type: string - format: uri - readOnly: true msgid: title: Message ID type: string diff --git docs/api/schemas/v1.1/patchwork.yaml docs/api/schemas/v1.1/patchwork.yaml index 574a8ad8..7e2299c5 100644 --- docs/api/schemas/v1.1/patchwork.yaml +++ docs/api/schemas/v1.1/patchwork.yaml @@ -2044,11 +2044,6 @@ components: title: ID type: integer readOnly: true - url: - title: URL - type: string - format: uri - readOnly: true web_url: title: Web URL type: string diff --git docs/api/schemas/v1.2/patchwork.yaml docs/api/schemas/v1.2/patchwork.yaml index 7a4e8e8e..93c3e97e 100644 --- docs/api/schemas/v1.2/patchwork.yaml +++ docs/api/schemas/v1.2/patchwork.yaml @@ -2287,11 +2287,6 @@ components: title: ID type: integer readOnly: true - url: - title: URL - type: string - format: uri - readOnly: true web_url: title: Web URL type: string diff --git docs/api/schemas/v1.3/patchwork.yaml docs/api/schemas/v1.3/patchwork.yaml index 6bd0419d..8663406d 100644 --- docs/api/schemas/v1.3/patchwork.yaml +++ docs/api/schemas/v1.3/patchwork.yaml @@ -1627,6 +1627,11 @@ components: title: ID type: integer readOnly: true + url: + title: URL + type: string + format: uri + readOnly: true web_url: title: Web URL type: string diff --git patchwork/api/base.py patchwork/api/base.py index 0f5c44a2..16e5cb8d 100644 --- patchwork/api/base.py +++ patchwork/api/base.py @@ -151,19 +151,17 @@ class NestedHyperlinkedIdentityField(HyperlinkedIdentityField): class BaseHyperlinkedModelSerializer(HyperlinkedModelSerializer): def to_representation(self, instance): - data = super(BaseHyperlinkedModelSerializer, self).to_representation( - instance - ) - request = self.context.get('request') for version in getattr(self.Meta, 'versioned_fields', {}): # if the user has requested a version lower that than in which the # field was added, we drop it if not utils.has_version(request, version): for field in self.Meta.versioned_fields[version]: - # After a PATCH with an older API version, we may not see - # these fields. If they don't exist, don't panic, return - # (and then discard) None. - data.pop(field, None) + if field in self.fields: + del self.fields[field] + + data = super(BaseHyperlinkedModelSerializer, self).to_representation( + instance + ) return data diff --git patchwork/api/bundle.py patchwork/api/bundle.py index b6c7c9d2..134b2724 100644 --- patchwork/api/bundle.py +++ patchwork/api/bundle.py @@ -99,7 +99,7 @@ class BundleSerializer(BaseHyperlinkedModelSerializer): if len(set([p.project.id for p in value])) > 1: raise ValidationError( - 'Bundle patches must belong to the same ' 'project' + 'Bundle patches must belong to the same project' ) return value diff --git patchwork/api/comment.py patchwork/api/comment.py index 13c116ee..eae83719 100644 --- patchwork/api/comment.py +++ patchwork/api/comment.py @@ -12,6 +12,7 @@ from rest_framework.serializers import HiddenField from rest_framework.serializers import SerializerMethodField from patchwork.api.base import BaseHyperlinkedModelSerializer +from patchwork.api.base import NestedHyperlinkedIdentityField from patchwork.api.base import MultipleFieldLookupMixin from patchwork.api.base import PatchworkPermission from patchwork.api.base import CurrentCoverDefault @@ -58,6 +59,7 @@ class BaseCommentListSerializer(BaseHyperlinkedModelSerializer): class Meta: fields = ( 'id', + 'url', 'web_url', 'msgid', 'list_archive_url', @@ -70,6 +72,7 @@ class BaseCommentListSerializer(BaseHyperlinkedModelSerializer): ) read_only_fields = ( 'id', + 'url', 'web_url', 'msgid', 'list_archive_url', @@ -82,17 +85,27 @@ class BaseCommentListSerializer(BaseHyperlinkedModelSerializer): versioned_fields = { '1.1': ('web_url',), '1.2': ('list_archive_url',), - '1.3': ('addressed',), + '1.3': ( + 'addressed', + 'url', + ), } class CoverCommentSerializer(BaseCommentListSerializer): + url = NestedHyperlinkedIdentityField( + 'api-cover-comment-detail', + lookup_field_mapping={ + 'cover_id': 'cover_id', + 'comment_id': 'id', + }, + ) cover = HiddenField(default=CurrentCoverDefault()) class Meta: model = CoverComment - fields = BaseCommentListSerializer.Meta.fields + ('cover', 'addressed') + fields = BaseCommentListSerializer.Meta.fields + ('cover',) read_only_fields = BaseCommentListSerializer.Meta.read_only_fields + ( 'cover', ) @@ -123,6 +136,13 @@ class CoverCommentMixin(object): class PatchCommentSerializer(BaseCommentListSerializer): + url = NestedHyperlinkedIdentityField( + 'api-patch-comment-detail', + lookup_field_mapping={ + 'patch_id': 'patch_id', + 'comment_id': 'id', + }, + ) patch = HiddenField(default=CurrentPatchDefault()) class Meta: diff --git patchwork/api/embedded.py patchwork/api/embedded.py index 4cfdf8e6..52018435 100644 --- patchwork/api/embedded.py +++ patchwork/api/embedded.py @@ -163,6 +163,7 @@ class CoverCommentSerializer(SerializedRelatedField): 'mbox', ), '1.2': ('list_archive_url',), + '1.3': ('url',), } extra_kwargs = { 'url': {'view_name': 'api-cover-comment-detail'}, @@ -225,6 +226,7 @@ class PatchCommentSerializer(SerializedRelatedField): 'mbox', ), '1.2': ('list_archive_url',), + '1.3': ('url',), } extra_kwargs = { 'url': {'view_name': 'api-patch-comment-detail'}, diff --git patchwork/api/patch.py patchwork/api/patch.py index 9fd10e06..34067611 100644 --- patchwork/api/patch.py +++ patchwork/api/patch.py @@ -180,6 +180,7 @@ class PatchListSerializer(BaseHyperlinkedModelSerializer): 'related', ) read_only_fields = ( + 'url', 'web_url', 'project', 'msgid', From patchwork Fri Sep 30 16:19:21 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Finucane X-Patchwork-Id: 1684927 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.ozlabs.org (client-ip=2404:9400:2:0:216:3eff:fee1:b9f1; helo=lists.ozlabs.org; envelope-from=patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org; receiver=) Authentication-Results: legolas.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=rSTU1dGZ; dkim-atps=neutral Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2404:9400:2:0:216:3eff:fee1:b9f1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384)) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4MfFnz2v6dz1ypH for ; Sat, 1 Oct 2022 02:20:39 +1000 (AEST) Received: from boromir.ozlabs.org (localhost [IPv6:::1]) by lists.ozlabs.org (Postfix) with ESMTP id 4MfFnz2Pw5z3c96 for ; Sat, 1 Oct 2022 02:20:39 +1000 (AEST) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=rSTU1dGZ; dkim-atps=neutral X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=none (no SPF record) smtp.mailfrom=that.guru (client-ip=136.175.108.160; helo=mail-108-mta160.mxroute.com; envelope-from=stephen@that.guru; receiver=) Authentication-Results: lists.ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=rSTU1dGZ; dkim-atps=neutral Received: from mail-108-mta160.mxroute.com (mail-108-mta160.mxroute.com [136.175.108.160]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 4MfFmz2FYWz3cGC for ; Sat, 1 Oct 2022 02:19:46 +1000 (AEST) Received: from mail-111-mta2.mxroute.com ([136.175.111.2] filter006.mxroute.com) (Authenticated sender: mN4UYu2MZsgR) by mail-108-mta160.mxroute.com (ZoneMTA) with ESMTPSA id 1838f322d660002b7a.003 for (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Fri, 30 Sep 2022 16:19:33 +0000 X-Zone-Loop: 9a666af2462c4ed1585a0cd77aebcaa2e75340bba1a7 X-Originating-IP: [136.175.111.2] DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=that.guru; s=x; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=zq249odPk7+M/T51JgO3fJgkXzEVUuPiSLyf3EhEAlY=; b=rSTU1dGZ5lOJ/zFvHh6cUPkGz2 AobByRKWW05Bz9vlwC3cjQK4RkelP780RAq1fswH19FX187tTRtk9Chd+tMYG0T5YtXh5oRFoLmV5 zLqRXKWNJTSL4sYGSoZWmk7q/629QZcYJSQ4sOd02MZzt2bUdinOvVjzrvSxiYDZUMOSZWvM9a+WT jQr7xzDoLvYee4qK+JP4vxNdu4dHQw522MXC8Xm8cTEDLSgXLt1cDz4ttsiIeAYuo1WNMCAl5j0ns E5VdM0eVftf8wJ0fbSs0jd+l2tvJUAaZu7lYgYpnDhidyYtgQgSdmPEkkHhdApn43VLQCblHMH9ad TrIwE2IA==; From: Stephen Finucane To: patchwork@lists.ozlabs.org Subject: [PATCH 10/10] urls: Encode slashes in message IDs Date: Fri, 30 Sep 2022 17:19:21 +0100 Message-Id: <20220930161921.266633-10-stephen@that.guru> In-Reply-To: <20220930161921.266633-1-stephen@that.guru> References: <20220930161921.266633-1-stephen@that.guru> MIME-Version: 1.0 X-Authenticated-Id: stephen@that.guru X-BeenThere: patchwork@lists.ozlabs.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Patchwork development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: siddhesh@gotplt.org Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" We were attempting to work around the fact that message IDs could contain slashes which in some cases broke our ability to generate meaningful URLs. Rather than doing this, insist that users encode these slashes so that we can distinguish between semantically meaningful slashes and those that form the URL. This is a slightly breaking change, but the current behavior is already broken (see the linked bug) so this seems reasonable. Signed-off-by: Stephen Finucane Closes: #433 Cc: dja@axtens.net Cc: siddhesh@gotplt.org --- notes/issue-433-5f048abbe3789556.yaml | 19 +++++++++++++++ patchwork/models.py | 23 ++++++++++++++----- .../patchwork/partials/download-buttons.html | 6 ++--- .../patchwork/partials/patch-list.html | 2 +- patchwork/templates/patchwork/submission.html | 8 +++---- patchwork/tests/api/test_cover.py | 2 +- patchwork/tests/api/test_patch.py | 2 +- patchwork/tests/views/test_bundles.py | 8 +++---- patchwork/tests/views/test_cover.py | 10 ++++---- patchwork/tests/views/test_patch.py | 22 +++++++++--------- patchwork/urls.py | 20 +++++----------- patchwork/views/comment.py | 4 ++-- patchwork/views/cover.py | 4 ++-- patchwork/views/patch.py | 8 +++---- 14 files changed, 80 insertions(+), 58 deletions(-) create mode 100644 notes/issue-433-5f048abbe3789556.yaml diff --git notes/issue-433-5f048abbe3789556.yaml notes/issue-433-5f048abbe3789556.yaml new file mode 100644 index 00000000..1d0c1553 --- /dev/null +++ notes/issue-433-5f048abbe3789556.yaml @@ -0,0 +1,19 @@ +--- +fixes: + - | + Message IDs containing slashes will now have these slashes percent-encoded. + Previously, attempts to access submissions whose Message IDs contained + slashes would result in a HTTP 404 on some Django versions. If you wish to + access such a submission, you must now percent-encode the slashes first. + For example, to access a patch, cover letter or comment with the following + message ID: + + bug-28101-10460-NydYNmfPGz@http.sourceware.org/bugzilla/ + + You should now use: + + bug-28101-10460-NydYNmfPGz@http.sourceware.org%2Dbugzilla%2D + + Both the web UI and REST API have been updated to generate URLs in this + format so this should only be noticable to users manually generating such + URLs. diff --git patchwork/models.py patchwork/models.py index d2507d4f..20ec9f06 100644 --- patchwork/models.py +++ patchwork/models.py @@ -369,12 +369,24 @@ class EmailMixin(models.Model): @property def url_msgid(self): - """A trimmed messageid, suitable for inclusion in URLs""" + """A trimmed Message ID, suitable for inclusion in URLs""" if settings.DEBUG: assert self.msgid[0] == '<' and self.msgid[-1] == '>' return self.msgid.strip('<>') + @property + def encoded_msgid(self): + """Like 'url_msgid' but with slashes percentage encoded.""" + # We don't want to encode all characters (i.e. use urllib.parse.quote) + # because that would result in us encoding the '@' present in all + # message IDs. Instead we only percent-encode any slashes present [1]. + # These are not common so this is very much expected to be an edge + # case. + # + # [1] https://datatracker.ietf.org/doc/html/rfc3986.html#section-2 + return self.url_msgid.replace('/', '%2F') + def save(self, *args, **kwargs): # Modifying a submission via admin interface changes '\n' newlines in # message content to '\r\n'. We need to fix them to avoid problems, @@ -436,7 +448,7 @@ class Cover(SubmissionMixin): 'cover-detail', kwargs={ 'project_id': self.project.linkname, - 'msgid': self.url_msgid, + 'msgid': self.encoded_msgid, }, ) @@ -445,7 +457,7 @@ class Cover(SubmissionMixin): 'cover-mbox', kwargs={ 'project_id': self.project.linkname, - 'msgid': self.url_msgid, + 'msgid': self.encoded_msgid, }, ) @@ -671,7 +683,7 @@ class Patch(SubmissionMixin): 'patch-detail', kwargs={ 'project_id': self.project.linkname, - 'msgid': self.url_msgid, + 'msgid': self.encoded_msgid, }, ) @@ -680,7 +692,7 @@ class Patch(SubmissionMixin): 'patch-mbox', kwargs={ 'project_id': self.project.linkname, - 'msgid': self.url_msgid, + 'msgid': self.encoded_msgid, }, ) @@ -760,7 +772,6 @@ class CoverComment(EmailMixin, models.Model): class PatchComment(EmailMixin, models.Model): - # parent patch = models.ForeignKey( Patch, diff --git patchwork/templates/patchwork/partials/download-buttons.html patchwork/templates/patchwork/partials/download-buttons.html index 149bbc62..34c5f8fc 100644 --- patchwork/templates/patchwork/partials/download-buttons.html +++ patchwork/templates/patchwork/partials/download-buttons.html @@ -4,16 +4,16 @@ {{ submission.id }} {% if submission.diff %} - diff - mbox {% else %} - mbox diff --git patchwork/templates/patchwork/partials/patch-list.html patchwork/templates/patchwork/partials/patch-list.html index a9a262eb..a882cd9d 100644 --- patchwork/templates/patchwork/partials/patch-list.html +++ patchwork/templates/patchwork/partials/patch-list.html @@ -186,7 +186,7 @@ $(document).ready(function() { {% endif %} - + {{ patch.name|default:"[no subject]"|truncatechars:100 }} diff --git patchwork/templates/patchwork/submission.html patchwork/templates/patchwork/submission.html index 266744d9..6ebd8415 100644 --- patchwork/templates/patchwork/submission.html +++ patchwork/templates/patchwork/submission.html @@ -72,7 +72,7 @@ {% if cover == submission %} {{ cover.name|default:"[no subject]"|truncatechars:100 }} {% else %} - + {{ cover.name|default:"[no subject]"|truncatechars:100 }} {% endif %} @@ -84,7 +84,7 @@ {% if sibling == submission %} {{ sibling.name|default:"[no subject]"|truncatechars:100 }} {% else %} - + {{ sibling.name|default:"[no subject]"|truncatechars:100 }} {% endif %} @@ -105,7 +105,7 @@ {% for sibling in related_same_project %}
  • {% if sibling.id != submission.id %} - + {{ sibling.name|default:"[no subject]"|truncatechars:100 }} {% endif %} @@ -116,7 +116,7 @@
  • - + {{ sibling.name|default:"[no subject]"|truncatechars:100 }} (in {{ sibling.project }})
  • diff --git patchwork/tests/api/test_cover.py patchwork/tests/api/test_cover.py index 126b3af1..44ae2ebf 100644 --- patchwork/tests/api/test_cover.py +++ patchwork/tests/api/test_cover.py @@ -116,7 +116,7 @@ class TestCoverAPI(utils.APITestCase): """Filter covers by msgid.""" cover = create_cover() - resp = self.client.get(self.api_url(), {'msgid': cover.url_msgid}) + resp = self.client.get(self.api_url(), {'msgid': cover.encoded_msgid}) self.assertEqual([cover.id], [x['id'] for x in resp.data]) # empty response if nothing matches diff --git patchwork/tests/api/test_patch.py patchwork/tests/api/test_patch.py index 0ba3042b..03b5be11 100644 --- patchwork/tests/api/test_patch.py +++ patchwork/tests/api/test_patch.py @@ -218,7 +218,7 @@ class TestPatchAPI(utils.APITestCase): """Filter patches by msgid.""" patch = self._create_patch() - resp = self.client.get(self.api_url(), {'msgid': patch.url_msgid}) + resp = self.client.get(self.api_url(), {'msgid': patch.encoded_msgid}) self.assertEqual([patch.id], [x['id'] for x in resp.data]) # empty response if nothing matches diff --git patchwork/tests/views/test_bundles.py patchwork/tests/views/test_bundles.py index b26badc8..b730bdf3 100644 --- patchwork/tests/views/test_bundles.py +++ patchwork/tests/views/test_bundles.py @@ -496,7 +496,7 @@ class BundleCreateFromPatchTest(BundleTestBase): 'patch-detail', kwargs={ 'project_id': patch.project.linkname, - 'msgid': patch.url_msgid, + 'msgid': patch.encoded_msgid, }, ), params, @@ -519,7 +519,7 @@ class BundleCreateFromPatchTest(BundleTestBase): 'patch-detail', kwargs={ 'project_id': patch.project.linkname, - 'msgid': patch.url_msgid, + 'msgid': patch.encoded_msgid, }, ), params, @@ -655,7 +655,7 @@ class BundleAddFromPatchTest(BundleTestBase): 'patch-detail', kwargs={ 'project_id': patch.project.linkname, - 'msgid': patch.url_msgid, + 'msgid': patch.encoded_msgid, }, ), params, @@ -680,7 +680,7 @@ class BundleAddFromPatchTest(BundleTestBase): 'patch-detail', kwargs={ 'project_id': patch.project.linkname, - 'msgid': patch.url_msgid, + 'msgid': patch.encoded_msgid, }, ), params, diff --git patchwork/tests/views/test_cover.py patchwork/tests/views/test_cover.py index f33a6238..ee1f205f 100644 --- patchwork/tests/views/test_cover.py +++ patchwork/tests/views/test_cover.py @@ -19,14 +19,14 @@ class CoverViewTest(TestCase): 'patch-detail', kwargs={ 'project_id': cover.project.linkname, - 'msgid': cover.url_msgid, + 'msgid': cover.encoded_msgid, }, ) redirect_url = reverse( 'cover-detail', kwargs={ 'project_id': cover.project.linkname, - 'msgid': cover.url_msgid, + 'msgid': cover.encoded_msgid, }, ) @@ -43,7 +43,7 @@ class CoverViewTest(TestCase): 'cover-detail', kwargs={ 'project_id': cover.project.linkname, - 'msgid': cover.url_msgid, + 'msgid': cover.encoded_msgid, }, ) @@ -60,7 +60,7 @@ class CoverViewTest(TestCase): 'cover-mbox', kwargs={ 'project_id': cover.project.linkname, - 'msgid': cover.url_msgid, + 'msgid': cover.encoded_msgid, }, ) @@ -98,7 +98,7 @@ class CommentRedirectTest(TestCase): 'cover-detail', kwargs={ 'project_id': cover.project.linkname, - 'msgid': cover.url_msgid, + 'msgid': cover.encoded_msgid, }, ), comment_id, diff --git patchwork/tests/views/test_patch.py patchwork/tests/views/test_patch.py index d1de8ec9..70a2c836 100644 --- patchwork/tests/views/test_patch.py +++ patchwork/tests/views/test_patch.py @@ -212,14 +212,14 @@ class PatchViewTest(TestCase): 'cover-detail', kwargs={ 'project_id': patch.project.linkname, - 'msgid': patch.url_msgid, + 'msgid': patch.encoded_msgid, }, ) redirect_url = reverse( 'patch-detail', kwargs={ 'project_id': patch.project.linkname, - 'msgid': patch.url_msgid, + 'msgid': patch.encoded_msgid, }, ) @@ -238,7 +238,7 @@ class PatchViewTest(TestCase): 'patch-detail', kwargs={ 'project_id': patch.project.linkname, - 'msgid': patch.url_msgid, + 'msgid': patch.encoded_msgid, }, ), comment_id, @@ -257,7 +257,7 @@ class PatchViewTest(TestCase): 'patch-detail', kwargs={ 'project_id': patch.project.linkname, - 'msgid': patch.url_msgid, + 'msgid': patch.encoded_msgid, }, ) @@ -274,7 +274,7 @@ class PatchViewTest(TestCase): 'patch-mbox', kwargs={ 'project_id': patch.project.linkname, - 'msgid': patch.url_msgid, + 'msgid': patch.encoded_msgid, }, ) @@ -291,7 +291,7 @@ class PatchViewTest(TestCase): 'patch-raw', kwargs={ 'project_id': patch.project.linkname, - 'msgid': patch.url_msgid, + 'msgid': patch.encoded_msgid, }, ) @@ -315,7 +315,7 @@ class PatchViewTest(TestCase): 'patch-detail', kwargs={ 'project_id': patch.project.linkname, - 'msgid': patch.url_msgid, + 'msgid': patch.encoded_msgid, }, ) response = self.client.get(requested_url) @@ -358,7 +358,7 @@ class PatchViewTest(TestCase): 'patch-detail', kwargs={ 'project_id': patch.project.linkname, - 'msgid': patch.url_msgid, + 'msgid': patch.encoded_msgid, }, ) response = self.client.get(requested_url) @@ -511,7 +511,7 @@ class UTF8PatchViewTest(TestCase): response = self.client.get( reverse( 'patch-detail', - args=[self.patch.project.linkname, self.patch.url_msgid], + args=[self.patch.project.linkname, self.patch.encoded_msgid], ) ) self.assertContains(response, self.patch.name) @@ -520,7 +520,7 @@ class UTF8PatchViewTest(TestCase): response = self.client.get( reverse( 'patch-mbox', - args=[self.patch.project.linkname, self.patch.url_msgid], + args=[self.patch.project.linkname, self.patch.encoded_msgid], ) ) self.assertEqual(response.status_code, 200) @@ -530,7 +530,7 @@ class UTF8PatchViewTest(TestCase): response = self.client.get( reverse( 'patch-raw', - args=[self.patch.project.linkname, self.patch.url_msgid], + args=[self.patch.project.linkname, self.patch.encoded_msgid], ) ) self.assertEqual(response.status_code, 200) diff --git patchwork/urls.py patchwork/urls.py index ab606f1c..f4d67aa7 100644 --- patchwork/urls.py +++ patchwork/urls.py @@ -47,29 +47,21 @@ urlpatterns = [ name='project-detail', ), # patch views - # NOTE(dja): Per the RFC, msgids can contain slashes. There doesn't seem - # to be an easy way to tell Django to urlencode the slash when generating - # URLs, so instead we must use a permissive regex (.+ rather than [^/]+). - # This also means we need to put the raw and mbox URLs first, otherwise the - # patch-detail regex will just greedily grab those parts into a massive and - # wrong msgid. - # - # This does mean that message-ids that end in '/raw/' or '/mbox/' will not - # work, but it is RECOMMENDED by the RFC that the right hand side of the @ - # contains a domain, so I think breaking on messages that have "domains" - # ending in /raw/ or /mbox/ is good enough. + # NOTE(stephenfin): Per the RFC, msgids can contain slashes. Users are + # required to percent-encode any slashes present to generate valid URLs. + # The API does this automatically. path( - 'project//patch//raw/', + 'project//patch//raw/', patch_views.patch_raw, name='patch-raw', ), path( - 'project//patch//mbox/', + 'project//patch//mbox/', patch_views.patch_mbox, name='patch-mbox', ), path( - 'project//patch//', + 'project//patch//', patch_views.patch_detail, name='patch-detail', ), diff --git patchwork/views/comment.py patchwork/views/comment.py index 4f699224..98232a9e 100644 --- patchwork/views/comment.py +++ patchwork/views/comment.py @@ -29,7 +29,7 @@ def comment(request, comment_id): 'patch-detail', kwargs={ 'project_id': patch.project.linkname, - 'msgid': patch.url_msgid, + 'msgid': patch.encoded_msgid, }, ) else: # cover @@ -37,7 +37,7 @@ def comment(request, comment_id): 'cover-detail', kwargs={ 'project_id': cover.project.linkname, - 'msgid': cover.url_msgid, + 'msgid': cover.encoded_msgid, }, ) diff --git patchwork/views/cover.py patchwork/views/cover.py index 3368186b..15013a89 100644 --- patchwork/views/cover.py +++ patchwork/views/cover.py @@ -71,7 +71,7 @@ def cover_by_id(request, cover_id): 'cover-detail', kwargs={ 'project_id': cover.project.linkname, - 'msgid': cover.url_msgid, + 'msgid': cover.encoded_msgid, }, ) @@ -85,7 +85,7 @@ def cover_mbox_by_id(request, cover_id): 'cover-mbox', kwargs={ 'project_id': cover.project.linkname, - 'msgid': cover.url_msgid, + 'msgid': cover.encoded_msgid, }, ) diff --git patchwork/views/patch.py patchwork/views/patch.py index 75705720..9f1bb415 100644 --- patchwork/views/patch.py +++ patchwork/views/patch.py @@ -40,7 +40,7 @@ def patch_list(request, project_id): def patch_detail(request, project_id, msgid): project = get_object_or_404(Project, linkname=project_id) - db_msgid = '<%s>' % msgid + db_msgid = f"<{msgid.replace('%2F', '/')}>" # redirect to cover letters where necessary try: @@ -190,7 +190,7 @@ def patch_by_id(request, patch_id): 'patch-detail', kwargs={ 'project_id': patch.project.linkname, - 'msgid': patch.url_msgid, + 'msgid': patch.encoded_msgid, }, ) @@ -204,7 +204,7 @@ def patch_mbox_by_id(request, patch_id): 'patch-mbox', kwargs={ 'project_id': patch.project.linkname, - 'msgid': patch.url_msgid, + 'msgid': patch.encoded_msgid, }, ) @@ -218,7 +218,7 @@ def patch_raw_by_id(request, patch_id): 'patch-raw', kwargs={ 'project_id': patch.project.linkname, - 'msgid': patch.url_msgid, + 'msgid': patch.encoded_msgid, }, )