From patchwork Mon Oct 7 22:16:43 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Johan Herland X-Patchwork-Id: 1172975 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 46nFH73hPBz9s7T for ; Tue, 8 Oct 2019 09:18:11 +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 46nFH72pz3zDqBP for ; Tue, 8 Oct 2019 09:18:11 +1100 (AEDT) X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=gmail.com (client-ip=209.85.208.180; helo=mail-lj1-f180.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-lj1-f180.google.com (mail-lj1-f180.google.com [209.85.208.180]) (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 46nFFg31cHzDqJK for ; Tue, 8 Oct 2019 09:16:53 +1100 (AEDT) Received: by mail-lj1-f180.google.com with SMTP id a22so15400972ljd.0 for ; Mon, 07 Oct 2019 15:16:53 -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=fOewKdbPpTjWdGHaJasRTo/HxVnpcS7HnJ6ZFgEjf+M=; b=ixw7IbiL+VYIqP9+8i6JhteXBdV3b7Wzo6K7BebyFq+QrtOH40UBubn4zolS5/Ao2S dfZmrIu1e/XvRIa99sNIWn+boJ5vVPiP0NWtKylnTYMwz7n+eP8vhK+ZjDGdSN8rwQRr Vu60XicPTyLMgm8wE4q+bAvIUFNeCm3M/uTjk9HdYj9Kc944woyS4rgsUrnQ91YrfgI2 UShWlJBiFmSGu6J03eE0emVS4OLqNcXl4WeOHIvMxZ3cpHhg9QW8rq2bmYZ9raQbWM+4 2MKB56c3NhMGETjD1XlnvZcxLKEH1YhYVmapH0+K/43EBKGplr01F8X3cXLzrgjG0N8P 1BMg== X-Gm-Message-State: APjAAAX/Yz+Hb9efGdMGIPqAVAndU5tDvxGM+YQDiZ+QayqWT7ToKMPi 7WBFZXI0xv/eonus96whrDKJxOANMiY= X-Google-Smtp-Source: APXvYqxkQjqxyAoJxg59CnV93FofTZ3a1rxeLydtQFsSqgNYKKvZInxgVCW9HvN9t7Inls2jNU4u4g== X-Received: by 2002:a05:651c:104b:: with SMTP id x11mr10741230ljm.218.1570486610211; Mon, 07 Oct 2019 15:16:50 -0700 (PDT) Received: from beta.herland (160.37-191-159.fiber.lynet.no. [37.191.159.160]) by smtp.gmail.com with ESMTPSA id s7sm3370081ljs.16.2019.10.07.15.16.49 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 07 Oct 2019 15:16:49 -0700 (PDT) From: Johan Herland To: patchwork@lists.ozlabs.org Subject: [PATCH v2 1/3] models.Event: Add the user responsible for the event Date: Tue, 8 Oct 2019 00:16:43 +0200 Message-Id: <20191007221645.13186-2-johan@herland.net> X-Mailer: git-send-email 2.19.2 In-Reply-To: <20191007221645.13186-1-johan@herland.net> References: <20191007221645.13186-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 --- 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 Mon Oct 7 22:16:44 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Johan Herland X-Patchwork-Id: 1172974 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 46nFGl3y5hz9s7T for ; Tue, 8 Oct 2019 09:17:51 +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 46nFGl2zmbzDqHK for ; Tue, 8 Oct 2019 09:17:51 +1100 (AEDT) X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=gmail.com (client-ip=209.85.167.53; helo=mail-lf1-f53.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-lf1-f53.google.com (mail-lf1-f53.google.com [209.85.167.53]) (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 46nFFg34z6zDqJL for ; Tue, 8 Oct 2019 09:16:54 +1100 (AEDT) Received: by mail-lf1-f53.google.com with SMTP id q12so5449677lfc.11 for ; Mon, 07 Oct 2019 15:16: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=Xz+CldcsyBTor7b40MR8pllz10TlP0gXlYea1OMysOc=; b=VdsnsUjseW2w9eoXxY8OFRYf5M7DXffVYuXQ07ZqGBKoI4WQloffTAlkj9Ti1ZE0Ol NJd1KeJHB6xBrBXSlQ0Xb4X/hm8ZrLRlK5rcHf2eOzcAdNQQz++6Erv9anF5UAALS8by +c+eUPmC7l6w+bRznInuf7F36rdBumSIvW2QpiCyyRgLstnT84+UvE9cesBDUq2dHvpw NOwsxuiPSp5sq2DOsiU2qLsXNJd9nvE0BfsbJEzbETq89XNWTO1E5ofBjzT1bkzjnPUi gDcZyp6cq7DVbrNxOGQg9T5zte+VZeVX+NybFeuJ2QfNpglEoPyfLPAsDBuuD+erXwn5 n1DQ== X-Gm-Message-State: APjAAAUvLotqRRi+UMrfcLokURgsLDu8T7Jdl88QYV1K0s8IX0YZs6K7 Ee3BSQn2oAdsg8FjboLpKV4EsHy56AM= X-Google-Smtp-Source: APXvYqxycObUyzDhMrbWc1CpTzp4fhBdQ+CpbV9lMrwbLueofHny4cZOssrgILVuvFVZmfvvegLqtA== X-Received: by 2002:a19:98e:: with SMTP id 136mr18623568lfj.156.1570486611434; Mon, 07 Oct 2019 15:16:51 -0700 (PDT) Received: from beta.herland (160.37-191-159.fiber.lynet.no. [37.191.159.160]) by smtp.gmail.com with ESMTPSA id s7sm3370081ljs.16.2019.10.07.15.16.50 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 07 Oct 2019 15:16:50 -0700 (PDT) From: Johan Herland To: patchwork@lists.ozlabs.org Subject: [PATCH v2 2/3] Include the responsible actor in applicable events Date: Tue, 8 Oct 2019 00:16:44 +0200 Message-Id: <20191007221645.13186-3-johan@herland.net> X-Mailer: git-send-email 2.19.2 In-Reply-To: <20191007221645.13186-1-johan@herland.net> References: <20191007221645.13186-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 Mon Oct 7 22:16:45 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Johan Herland X-Patchwork-Id: 1172977 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)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 46nFHm60RPz9s7T for ; Tue, 8 Oct 2019 09:18:44 +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 46nFHm4jgTzDqJW for ; Tue, 8 Oct 2019 09:18:44 +1100 (AEDT) X-Original-To: patchwork@lists.ozlabs.org Delivered-To: patchwork@lists.ozlabs.org Authentication-Results: lists.ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=gmail.com (client-ip=209.85.208.182; helo=mail-lj1-f182.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-lj1-f182.google.com (mail-lj1-f182.google.com [209.85.208.182]) (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 46nFFh3XMZzDqJC for ; Tue, 8 Oct 2019 09:16:56 +1100 (AEDT) Received: by mail-lj1-f182.google.com with SMTP id v24so15399440ljj.3 for ; Mon, 07 Oct 2019 15:16:55 -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=6bwzIcXvel9Zly/gXYnb6rIkaluCYulCMUL/6jdDZNo=; b=TctacfFvVRoftQ5EflW3LRnj35NrKCZWrF5IljAL2ttTtdmXycWuj7TFWwxCweGTiJ bYb0WYSbrUoIqjvUDBTUwwhhog1abXSNuEav5lu+cOp6FvErI87l5Fl+ohpxIg1uhTM8 9KBS4aZ0Eo8cupDg26rPDRZKLS4uoa5kz79iixkKPDpeKVKLlWqe3DHdY4wTDroh6TVW jMCkVIzL1heg1wmoozt1cb08UpMo8ZiulkHjEeM47Mo6ffFXxtRU6NB0SOH9qE3s71uJ rSn3c8uDtdH5tbKOqiuwjmX9pxmE437WtWtekpibJVzt5XqEAaVQHkEkmK2TCTZpUOua d4BA== X-Gm-Message-State: APjAAAU1ouTalhncJAQiUdiALTqbPLjvqYnNLdcq6FrAhO70tfMbEjnL pB9PS4jhjLeYcAmR68/tfJ4v5Yj5g/E= X-Google-Smtp-Source: APXvYqx17aYMDFX32KNiYUHFR8cXW813jvDyBnk9ptkekquOG23chfH7ANVVeCrDFyg0cRQ0+1oP8g== X-Received: by 2002:a2e:98d2:: with SMTP id s18mr20093409ljj.68.1570486612506; Mon, 07 Oct 2019 15:16:52 -0700 (PDT) Received: from beta.herland (160.37-191-159.fiber.lynet.no. [37.191.159.160]) by smtp.gmail.com with ESMTPSA id s7sm3370081ljs.16.2019.10.07.15.16.51 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 07 Oct 2019 15:16:51 -0700 (PDT) From: Johan Herland To: patchwork@lists.ozlabs.org Subject: [PATCH v2 3/3] /api/events: Add 'actor' field to generated JSON Date: Tue, 8 Oct 2019 00:16:45 +0200 Message-Id: <20191007221645.13186-4-johan@herland.net> X-Mailer: git-send-email 2.19.2 In-Reply-To: <20191007221645.13186-1-johan@herland.net> References: <20191007221645.13186-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 --- docs/api/schemas/latest/patchwork.yaml | 6 ++++++ docs/api/schemas/patchwork.j2 | 8 ++++++++ docs/api/schemas/v1.2/patchwork.yaml | 6 ++++++ patchwork/api/event.py | 10 +++++++--- patchwork/tests/api/test_event.py | 24 ++++++++++++++++++++++++ 5 files changed, 51 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/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)