From patchwork Wed Oct 16 22:44:40 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Johan Herland X-Patchwork-Id: 1178204 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.ozlabs.org (lists.ozlabs.org [203.11.71.2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 46tnSM0wFnz9sCJ for ; Thu, 17 Oct 2019 09:45:23 +1100 (AEDT) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herland.net Received: from bilbo.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3]) by lists.ozlabs.org (Postfix) with ESMTP id 46tnSK1l0zzDr6d for ; Thu, 17 Oct 2019 09:45:21 +1100 (AEDT) X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gmail.com (client-ip=209.85.221.47; helo=mail-wr1-f47.google.com; envelope-from=jherland@gmail.com; receiver=) Authentication-Results: lists.ozlabs.org; dmarc=none (p=none dis=none) header.from=herland.net Received: from mail-wr1-f47.google.com (mail-wr1-f47.google.com [209.85.221.47]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 46tnRp0Cx8zDr6d for ; Thu, 17 Oct 2019 09:44:51 +1100 (AEDT) Received: by mail-wr1-f47.google.com with SMTP id p14so102195wro.4 for ; Wed, 16 Oct 2019 15:44:51 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=UqoKK0JWowjKQq9ii1VG3EGWGArtaqB+TqFZydmMcXg=; b=Ot4NkUbwI4uxA9UM1dk3zdltXahioXaIYv1ZbM+OPCGpPv52ETe2pjwoKAbO6H+d6l jXTAfsC0y91mBneHb5JF6MjqSkBeB70FFOHAdumz4Ybe8ST6N7dtJpF5dzN3v+l1nYKA tM2FQHEw5qK0uJfHNHHKVonBlcWKepSVrD/P3MkRKUmaCprf5pTwGs2BMrOhXZ4mEh27 HfkSaO4SM9K3EixsAwV+OtleVx2HUzqm4aU99mcAJiNDp5VETHDT9I6LyA/rrJyYquFQ RVIGekRmc+Z60RTI+K7jiZVIq9fIG0y5GWnN53u0q9fpyDfiHpw4ZM6TJ9LKVZHu7JCX h5pw== X-Gm-Message-State: APjAAAU7xB/PKeftoRvVbvfMkJmIvuPgvzZgW4hpEhJPFi3kLgV7YaG7 Jh6pXiFBKf1owehrDc8gCkLCwlyjOTc= X-Google-Smtp-Source: APXvYqxGJTTEbI6zcEwSRwApK7X1FwC31Hkm2wd0XD/C19tJM/IteF3gPNnYf7waULHm2GLAi+GrSQ== X-Received: by 2002:a05:6000:1288:: with SMTP id f8mr165793wrx.111.1571265888307; Wed, 16 Oct 2019 15:44:48 -0700 (PDT) Received: from beta.cisco.com ([173.38.220.34]) by smtp.gmail.com with ESMTPSA id w4sm198364wrv.66.2019.10.16.15.44.47 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 16 Oct 2019 15:44:47 -0700 (PDT) From: Johan Herland To: patchwork@lists.ozlabs.org Subject: [PATCH v3 1/3] models.Event: Add the user responsible for the event Date: Thu, 17 Oct 2019 00:44:40 +0200 Message-Id: <20191016224442.9211-2-johan@herland.net> X-Mailer: git-send-email 2.19.2 In-Reply-To: <20191016224442.9211-1-johan@herland.net> References: <20191016224442.9211-1-johan@herland.net> MIME-Version: 1.0 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: Mauro Carvalho Chehab Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" This allows using the events as a kind of audit log, to see how a patch came to its current state/delegate. Cc: Mauro Carvalho Chehab Signed-off-by: Johan Herland Reviewed-by: Stephen Finucane Reviewed-by: Andrew Donnellan --- patchwork/migrations/0037_event_actor.py | 21 +++++++++++++++++++++ patchwork/models.py | 4 ++++ 2 files changed, 25 insertions(+) create mode 100644 patchwork/migrations/0037_event_actor.py diff --git a/patchwork/migrations/0037_event_actor.py b/patchwork/migrations/0037_event_actor.py new file mode 100644 index 0000000..6607228 --- /dev/null +++ b/patchwork/migrations/0037_event_actor.py @@ -0,0 +1,21 @@ +# Generated by Django 2.2.6 on 2019-10-08 04:21 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('patchwork', '0036_project_commit_url_format'), + ] + + operations = [ + migrations.AddField( + model_name='event', + name='actor', + field=models.ForeignKey(blank=True, help_text='The user that caused/created this event.', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/patchwork/models.py b/patchwork/models.py index c198bc2..b43c15a 100644 --- a/patchwork/models.py +++ b/patchwork/models.py @@ -944,6 +944,10 @@ class Event(models.Model): date = models.DateTimeField( default=datetime.datetime.utcnow, help_text='The time this event was created.') + actor = models.ForeignKey( + User, related_name='+', null=True, blank=True, + on_delete=models.SET_NULL, + help_text='The user that caused/created this event.') # event object From patchwork Wed Oct 16 22:44:41 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Johan Herland X-Patchwork-Id: 1178203 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 46tnRz19sxz9sPF for ; Thu, 17 Oct 2019 09:45:03 +1100 (AEDT) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herland.net Received: from bilbo.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3]) by lists.ozlabs.org (Postfix) with ESMTP id 46tnRy5VVvzDr75 for ; Thu, 17 Oct 2019 09:45:02 +1100 (AEDT) X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gmail.com (client-ip=209.85.221.42; helo=mail-wr1-f42.google.com; envelope-from=jherland@gmail.com; receiver=) Authentication-Results: lists.ozlabs.org; dmarc=none (p=none dis=none) header.from=herland.net Received: from mail-wr1-f42.google.com (mail-wr1-f42.google.com [209.85.221.42]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 46tnRp02GXzDr6b for ; Thu, 17 Oct 2019 09:44:53 +1100 (AEDT) Received: by mail-wr1-f42.google.com with SMTP id p4so88918wrm.8 for ; Wed, 16 Oct 2019 15:44:52 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=sd+s6utTBVZ3al/UGX1NXrY0VgHVxhn97PmhF+LXeic=; b=RgNF4j/KSM/OoskFOM43ymKMgLm9RzAvw7d9+4TZH2Yo1TJ0Vn475YYlcKjwHk9+sK ucamR2LTx2YWaycZ316tY4UdK4gBTvMAgqaQs53XlP1/2AIpaZJ4ekwu2d0W+iua1bPE VbC1NGp/tJgKu8R1i3BdH2HIv/lBszTEGbMFU53bPTGv5VVMxVlEQUEwWfCh2YVoV8S0 gf/LX8PVACboa2kBsyQ/c6ucemUx/b0HpiUP6nDQczJOGzyYibDMB6toAfzkbmCk5w1N CgySRXcj7j4ufmIpwlkVH4aJCgZcrdw5ej56ikOUb05ZTCDOoSSvTbOC4d6e1gtDqvTk OIKA== X-Gm-Message-State: APjAAAXRfqYv8qlO+Xh7oCqjiRgRp5n7p61epPEbck4HYN4y7zyLE8cG NXlsTLudQ9M1ugGzBiGQyHrO1pRxVHY= X-Google-Smtp-Source: APXvYqwn09sGz848bfXehckNmsR39q617JvDrl4h+386D3B73cwx8Wwo/1vmusb9xMBfKiyXulgnyQ== X-Received: by 2002:adf:f686:: with SMTP id v6mr188999wrp.141.1571265889573; Wed, 16 Oct 2019 15:44:49 -0700 (PDT) Received: from beta.cisco.com ([173.38.220.34]) by smtp.gmail.com with ESMTPSA id w4sm198364wrv.66.2019.10.16.15.44.48 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 16 Oct 2019 15:44:48 -0700 (PDT) From: Johan Herland To: patchwork@lists.ozlabs.org Subject: [PATCH v3 2/3] Include the responsible actor in applicable events Date: Thu, 17 Oct 2019 00:44:41 +0200 Message-Id: <20191016224442.9211-3-johan@herland.net> X-Mailer: git-send-email 2.19.2 In-Reply-To: <20191016224442.9211-1-johan@herland.net> References: <20191016224442.9211-1-johan@herland.net> MIME-Version: 1.0 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: Mauro Carvalho Chehab Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" We want to use the events as an audit log. An important part of this is recording _who_ made the changes that the events represent. To accomplish this, we need to know the current user (aka. request.user) at the point where we create the Event instance. Event creation is currently triggered by signals, but neither the signal handlers, nor the model classes themselves have easy access to request.user. For Patch-based events (patch-created, patch-state-changed, patch-delegated, patch-completed), we can do the following hack: The relevant events are created in signal handlers that are all hooked up to either the pre_save or post_save signals sent by Patch.save(). But before calling Patch.save(), Patchwork must naturally query Patch.is_editable() to ascertain whether the patch can in fact be changed by the current user. Thus, we only need a way to communicate the current user from Patch.is_editable() to the signal handlers that create the resulting Events. The Patch object itself is available in both places, so we simply add an ._edited_by attribute to the instance (which fortunately is not detected as a persistent db field by Django). The series-completed event is also triggered by Patch.save(), so uses the same trick as above. For the check-created event the current user always happens to be the same as the .user field recorded in the Check object itself. The remaining events (cover-created, series-created) are both triggered by incoming emails, hence have no real actor as such, so we simply leave the actor as None/NULL. Closes: #73 Cc: Mauro Carvalho Chehab Signed-off-by: Johan Herland --- patchwork/models.py | 6 +++++- patchwork/signals.py | 10 ++++++++-- patchwork/tests/test_events.py | 7 +++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/patchwork/models.py b/patchwork/models.py index b43c15a..f37572e 100644 --- a/patchwork/models.py +++ b/patchwork/models.py @@ -498,9 +498,13 @@ class Patch(Submission): return False if user in [self.submitter.user, self.delegate]: + self._edited_by = user return True - return self.project.is_editable(user) + if self.project.is_editable(user): + self._edited_by = user + return True + return False @property def combined_check_state(self): diff --git a/patchwork/signals.py b/patchwork/signals.py index 666199b..987cc5a 100644 --- a/patchwork/signals.py +++ b/patchwork/signals.py @@ -77,6 +77,7 @@ def create_patch_created_event(sender, instance, created, raw, **kwargs): return Event.objects.create( category=Event.CATEGORY_PATCH_CREATED, project=patch.project, + actor=getattr(patch, '_edited_by', None), patch=patch) # don't trigger for items loaded from fixtures or new items @@ -93,6 +94,7 @@ def create_patch_state_changed_event(sender, instance, raw, **kwargs): return Event.objects.create( category=Event.CATEGORY_PATCH_STATE_CHANGED, project=patch.project, + actor=getattr(patch, '_edited_by', None), patch=patch, previous_state=before, current_state=after) @@ -116,6 +118,7 @@ def create_patch_delegated_event(sender, instance, raw, **kwargs): return Event.objects.create( category=Event.CATEGORY_PATCH_DELEGATED, project=patch.project, + actor=getattr(patch, '_edited_by', None), patch=patch, previous_delegate=before, current_delegate=after) @@ -139,6 +142,7 @@ def create_patch_completed_event(sender, instance, raw, **kwargs): return Event.objects.create( category=Event.CATEGORY_PATCH_COMPLETED, project=patch.project, + actor=getattr(patch, '_edited_by', None), patch=patch, series=patch.series) @@ -184,6 +188,7 @@ def create_check_created_event(sender, instance, created, raw, **kwargs): return Event.objects.create( category=Event.CATEGORY_CHECK_CREATED, project=check.patch.project, + actor=check.user, patch=check.patch, created_check=check) @@ -219,10 +224,11 @@ def create_series_completed_event(sender, instance, raw, **kwargs): # exists in the wild ('PATCH 5/n'), so we probably want to retest a series # in that case. - def create_event(series): + def create_event(series, actor): return Event.objects.create( category=Event.CATEGORY_SERIES_COMPLETED, project=series.project, + actor=actor, series=series) # don't trigger for items loaded from fixtures, new items or items that @@ -241,4 +247,4 @@ def create_series_completed_event(sender, instance, raw, **kwargs): # we can't use "series.received_all" here since we haven't actually saved # the instance yet so we duplicate that logic here but with an offset if (instance.series.received_total + 1) >= instance.series.total: - create_event(instance.series) + create_event(instance.series, getattr(instance, '_edited_by', None)) diff --git a/patchwork/tests/test_events.py b/patchwork/tests/test_events.py index e5c40b5..415237f 100644 --- a/patchwork/tests/test_events.py +++ b/patchwork/tests/test_events.py @@ -110,8 +110,10 @@ class PatchChangedTest(_BaseTestCase): patch = utils.create_patch(series=None) old_state = patch.state new_state = utils.create_state() + actor = utils.create_maintainer(project=patch.project) patch.state = new_state + self.assertTrue(patch.is_editable(actor)) patch.save() events = _get_events(patch=patch) @@ -120,6 +122,7 @@ class PatchChangedTest(_BaseTestCase): self.assertEqual(events[1].category, Event.CATEGORY_PATCH_STATE_CHANGED) self.assertEqual(events[1].project, patch.project) + self.assertEqual(events[1].actor, actor) self.assertEventFields(events[1], previous_state=old_state, current_state=new_state) @@ -127,10 +130,12 @@ class PatchChangedTest(_BaseTestCase): # purposefully setting series to None to minimize additional events patch = utils.create_patch(series=None) delegate_a = utils.create_user() + actor = utils.create_maintainer(project=patch.project) # None -> Delegate A patch.delegate = delegate_a + self.assertTrue(patch.is_editable(actor)) patch.save() events = _get_events(patch=patch) @@ -139,6 +144,7 @@ class PatchChangedTest(_BaseTestCase): self.assertEqual(events[1].category, Event.CATEGORY_PATCH_DELEGATED) self.assertEqual(events[1].project, patch.project) + self.assertEqual(events[1].actor, actor) self.assertEventFields(events[1], current_delegate=delegate_a) delegate_b = utils.create_user() @@ -175,6 +181,7 @@ class CheckCreatedTest(_BaseTestCase): self.assertEqual(events.count(), 1) self.assertEqual(events[0].category, Event.CATEGORY_CHECK_CREATED) self.assertEqual(events[0].project, check.patch.project) + self.assertEqual(events[0].actor, check.user) self.assertEventFields(events[0]) From patchwork Wed Oct 16 22:44:42 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Johan Herland X-Patchwork-Id: 1178205 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.ozlabs.org (lists.ozlabs.org [203.11.71.2]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 46tnSk693Mz9sPF for ; Thu, 17 Oct 2019 09:45:42 +1100 (AEDT) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herland.net Received: from bilbo.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3]) by lists.ozlabs.org (Postfix) with ESMTP id 46tnSk3jfPzDr7L for ; Thu, 17 Oct 2019 09:45:42 +1100 (AEDT) X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=gmail.com (client-ip=209.85.221.49; helo=mail-wr1-f49.google.com; envelope-from=jherland@gmail.com; receiver=) Authentication-Results: lists.ozlabs.org; dmarc=none (p=none dis=none) header.from=herland.net Received: from mail-wr1-f49.google.com (mail-wr1-f49.google.com [209.85.221.49]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 46tnRq015gzDr6f for ; Thu, 17 Oct 2019 09:44:54 +1100 (AEDT) Received: by mail-wr1-f49.google.com with SMTP id p14so102298wro.4 for ; Wed, 16 Oct 2019 15:44:54 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=gkXHkDY0e/rBQifG5/WeSl5HzP2Z6PJDtp3fZDMnJDk=; b=AAtfe2aKvQ+kVmi6OZNYcSaywE8FuJxelvYEKxoRQaJDSPOo9PovPqhE35JOH151Qk snXtOMP3u9ZcswPlo3ED7W4nI2vxJwJDFmWCfriAVfRM1DGBB+Is8TQ31uVoF//wPtzk jVvzeRjueSRHNYlScGy60sXxC8SCGpjF5IRzrd0iPcp4owNx1Yddm8DE7lA1Ll/Ovc/6 bZLBBEcw7Kfqp6xrX1VXrF8OqVOSgXmY8oypQx6TdVySl3lAQX02f+ru23dCy/AxcnwP GUhDRFEw3cHCLHxKccbwg69dxrlcWXgCBm/TU87ua9WGa819ZJ/pKRMTSXfIvU/8BFE5 9awg== X-Gm-Message-State: APjAAAV9XMH8LKxqYafwlK8iq+9MjBPjiG/guwRNPgjN8pXYpZmgavw8 UTihzC3hva6IFRTYFYlnSVPaV+MztGA= X-Google-Smtp-Source: APXvYqyGM6fwiBsjq2lnet2K6MXA3X1H4EXsOxtmdl7ZQZb5a5gMUKpsnsRW+tntA1VzcN4ZeEquww== X-Received: by 2002:adf:fe12:: with SMTP id n18mr211230wrr.114.1571265890919; Wed, 16 Oct 2019 15:44:50 -0700 (PDT) Received: from beta.cisco.com ([173.38.220.34]) by smtp.gmail.com with ESMTPSA id w4sm198364wrv.66.2019.10.16.15.44.49 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 16 Oct 2019 15:44:50 -0700 (PDT) From: Johan Herland To: patchwork@lists.ozlabs.org Subject: [PATCH v3 3/3] /api/events: Add 'actor' field to generated JSON Date: Thu, 17 Oct 2019 00:44:42 +0200 Message-Id: <20191016224442.9211-4-johan@herland.net> X-Mailer: git-send-email 2.19.2 In-Reply-To: <20191016224442.9211-1-johan@herland.net> References: <20191016224442.9211-1-johan@herland.net> MIME-Version: 1.0 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: Mauro Carvalho Chehab Errors-To: patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Patchwork" Cc: Mauro Carvalho Chehab Signed-off-by: Johan Herland Acked-by: Daniel Axtens Reviewed-by: Andrew Donnellan --- docs/api/schemas/latest/patchwork.yaml | 6 ++++++ docs/api/schemas/patchwork.j2 | 8 ++++++++ docs/api/schemas/v1.2/patchwork.yaml | 6 ++++++ docs/usage/overview.rst | 3 +++ patchwork/api/event.py | 10 +++++++--- patchwork/tests/api/test_event.py | 24 ++++++++++++++++++++++++ 6 files changed, 54 insertions(+), 3 deletions(-) diff --git a/docs/api/schemas/latest/patchwork.yaml b/docs/api/schemas/latest/patchwork.yaml index 45a6118..58d5d44 100644 --- a/docs/api/schemas/latest/patchwork.yaml +++ b/docs/api/schemas/latest/patchwork.yaml @@ -1495,6 +1495,12 @@ components: type: string format: iso8601 readOnly: true + actor: + type: object + title: The user that caused/created this event + readOnly: true + allOf: + - $ref: '#/components/schemas/UserEmbedded' payload: type: object EventCoverCreated: diff --git a/docs/api/schemas/patchwork.j2 b/docs/api/schemas/patchwork.j2 index 16d85a3..1f1a7bd 100644 --- a/docs/api/schemas/patchwork.j2 +++ b/docs/api/schemas/patchwork.j2 @@ -1510,6 +1510,14 @@ components: type: string format: iso8601 readOnly: true +{% if version >= (1, 2) %} + actor: + type: object + title: The user that caused/created this event + readOnly: true + allOf: + - $ref: '#/components/schemas/UserEmbedded' +{% endif %} payload: type: object EventCoverCreated: diff --git a/docs/api/schemas/v1.2/patchwork.yaml b/docs/api/schemas/v1.2/patchwork.yaml index 3a96aa3..2aaf393 100644 --- a/docs/api/schemas/v1.2/patchwork.yaml +++ b/docs/api/schemas/v1.2/patchwork.yaml @@ -1495,6 +1495,12 @@ components: type: string format: iso8601 readOnly: true + actor: + type: object + title: The user that caused/created this event + readOnly: true + allOf: + - $ref: '#/components/schemas/UserEmbedded' payload: type: object EventCoverCreated: diff --git a/docs/usage/overview.rst b/docs/usage/overview.rst index e84e13d..273c792 100644 --- a/docs/usage/overview.rst +++ b/docs/usage/overview.rst @@ -228,6 +228,9 @@ properties: ``date`` When this event was created +``actor`` + The user, if any, that caused/created this event + ``payload`` Additional information diff --git a/patchwork/api/event.py b/patchwork/api/event.py index c0d973d..33ea104 100644 --- a/patchwork/api/event.py +++ b/patchwork/api/event.py @@ -23,6 +23,7 @@ from patchwork.models import Event class EventSerializer(ModelSerializer): project = ProjectSerializer(read_only=True) + actor = UserSerializer() patch = PatchSerializer(read_only=True) series = SeriesSerializer(read_only=True) cover = CoverLetterSerializer(read_only=True) @@ -50,7 +51,7 @@ class EventSerializer(ModelSerializer): data = super(EventSerializer, self).to_representation(instance) payload = OrderedDict() kept_fields = self._category_map[instance.category] + [ - 'id', 'category', 'project', 'date'] + 'id', 'category', 'project', 'date', 'actor'] for field in [x for x in data]: if field not in kept_fields: @@ -65,10 +66,13 @@ class EventSerializer(ModelSerializer): class Meta: model = Event - fields = ('id', 'category', 'project', 'date', 'patch', 'series', - 'cover', 'previous_state', 'current_state', + fields = ('id', 'category', 'project', 'date', 'actor', 'patch', + 'series', 'cover', 'previous_state', 'current_state', 'previous_delegate', 'current_delegate', 'created_check') read_only_fields = fields + versioned_fields = { + '1.2': ('actor', ), + } class EventList(ListAPIView): diff --git a/patchwork/tests/api/test_event.py b/patchwork/tests/api/test_event.py index 8816538..5e47ff3 100644 --- a/patchwork/tests/api/test_event.py +++ b/patchwork/tests/api/test_event.py @@ -35,11 +35,16 @@ class TestEventAPI(utils.APITestCase): def assertSerialized(self, event_obj, event_json): self.assertEqual(event_obj.id, event_json['id']) self.assertEqual(event_obj.category, event_json['category']) + if event_obj.actor is None: + self.assertIsNone(event_json['actor']) # nested fields self.assertEqual(event_obj.project.id, event_json['project']['id']) + if event_obj.actor is not None: + self.assertEqual(event_obj.actor.id, + event_json['actor']['id']) # TODO(stephenfin): Check other fields @@ -66,10 +71,12 @@ class TestEventAPI(utils.APITestCase): # check-created create_check(patch=patch) # patch-delegated, patch-state-changed + actor = create_maintainer(project=patch.project) user = create_maintainer(project=patch.project) state = create_state() patch.delegate = user patch.state = state + self.assertTrue(patch.is_editable(actor)) patch.save() return Event.objects.all() @@ -158,3 +165,20 @@ class TestEventAPI(utils.APITestCase): self.client.force_authenticate(user=user) resp = self.client.post(self.api_url(), {'category': 'patch-created'}) self.assertEqual(status.HTTP_405_METHOD_NOT_ALLOWED, resp.status_code) + + def test_change_delegate(self): + """Ensure changing patch delegate via API produces expected event""" + patch = create_patch() + delegate = create_maintainer(project=patch.project) + actor = create_maintainer(project=patch.project) + + self.client.force_authenticate(user=actor) + patch_url = reverse('api-patch-detail', kwargs={'pk': patch.id}) + resp = self.client.patch(patch_url, {'delegate': delegate.id}) + self.assertEqual(status.HTTP_200_OK, resp.status_code, resp) + + events = Event.objects.all() + delegation_event = events.get(category='patch-delegated') + self.assertEqual(actor.id, delegation_event.actor.id) + self.assertEqual(None, delegation_event.previous_delegate) + self.assertEqual(delegate.id, delegation_event.current_delegate.id)