From patchwork Thu Feb 27 23:29:31 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stephen Finucane X-Patchwork-Id: 1246177 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 48T8666Qx6z9sR4 for ; Fri, 28 Feb 2020 10:30:06 +1100 (AEDT) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=that.guru Authentication-Results: ozlabs.org; dkim=fail reason="key not found in DNS" header.d=that.guru header.i=@that.guru header.a=rsa-sha256 header.s=default header.b=iIeR6Bff; dkim-atps=neutral Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3]) by lists.ozlabs.org (Postfix) with ESMTP id 48T8664pC1zDr9T for ; Fri, 28 Feb 2020 10:30:06 +1100 (AEDT) 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=199.181.239.100; helo=relay0100.mxlogin.com; envelope-from=stephen@that.guru; receiver=) Authentication-Results: lists.ozlabs.org; dmarc=none (p=none dis=none) header.from=that.guru 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=default header.b=iIeR6Bff; dkim-atps=neutral Received: from relay0100.mxlogin.com (relay0100.mxlogin.com [199.181.239.100]) (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 48T85g4bfKzDqxk for ; Fri, 28 Feb 2020 10:29:42 +1100 (AEDT) Received: from filter004.mxroute.com ([149.28.56.236] 149.28.56.236.vultr.com) (Authenticated sender: mN4UYu2MZsgR) by relay0100.mxlogin.com (ZoneMTA) with ESMTPSA id 17088fbf61f000c6c5.003 for (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256); Thu, 27 Feb 2020 23:29:38 +0000 X-Zone-Loop: ff89fdbe7e18779b73cb9d90a572744123a8aa064f71 X-Originating-IP: [149.28.56.236] Received: from one.mxroute.com (one.mxroute.com [195.201.59.211]) by filter004.mxroute.com (Postfix) with ESMTPS id 2983C3E990; Thu, 27 Feb 2020 23:29:37 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=that.guru; s=default; 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=pYKdSREq7n5Cz0SNIC4WYkvG0oTy6+pHo6wUelq4hzk=; b=iIeR6Bffl2ZIcQp37dWlw/bTK4 CGL3QztzC9o+jBLYTOWmuNwYoPXxKYypx0dVDePT6WjYyZMAmVP3bBp1NPWGMub2ool7kLlRnrjzE TO2Oto+Lcb9TMI5vEafPG5pN/M7u+92FYamiBWEiKgmxb7B3yLGy7v5wlVU+uJEiBbvTLXs0AC0CB b9zeyCNgrHcfrCVzRo6icnFF/Gh9PKaY5bFvjpsQnsxUeDx+1lFadkM0utTU2cWJnbKuZmGZ6lNFF tQakVU1FuZrIKLWfJhyOTJdS6zK0YCuRCMh16i8fmCGnrSp+PVmwz7BozXdSGeB82kuzPwLXJWXzn JY+A/lpQ==; From: Stephen Finucane To: patchwork@lists.ozlabs.org Subject: [PATCH v2 1/2] models, templates: Add patch relations Date: Thu, 27 Feb 2020 23:29:31 +0000 Message-Id: <20200227232932.79562-2-stephen@that.guru> X-Mailer: git-send-email 2.24.1 In-Reply-To: <20200227232932.79562-1-stephen@that.guru> References: <20200227232932.79562-1-stephen@that.guru> MIME-Version: 1.0 X-AuthUser: 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" From: Mete Polat Introduces the ability to add relations between patches. Relations are displayed in the details page of a patch under 'Related'. Related patches located in another projects can be viewed as well. Changes to relations are tracked in events. Currently the display of this is very bare in the API but that will be fixed in a subsequent patch: this is the minimum required to avoid throwing errors when you view the events feed. Signed-off-by: Mete Polat [dja: address some review comments from Stephen, add an admin view, move to using Events, misc tidy-ups.] Signed-off-by: Daniel Axtens --- patchwork/admin.py | 8 ++++ patchwork/api/event.py | 5 ++- .../migrations/0040_add_related_patches.py | 41 +++++++++++++++++++ patchwork/models.py | 32 ++++++++++++++- patchwork/signals.py | 24 +++++++++++ patchwork/templates/patchwork/submission.html | 37 +++++++++++++++++ patchwork/views/patch.py | 14 +++++++ 7 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 patchwork/migrations/0040_add_related_patches.py diff --git a/patchwork/admin.py b/patchwork/admin.py index f9a94c6f..c3d45240 100644 --- a/patchwork/admin.py +++ b/patchwork/admin.py @@ -14,6 +14,7 @@ from patchwork.models import Comment from patchwork.models import CoverLetter from patchwork.models import DelegationRule from patchwork.models import Patch +from patchwork.models import PatchRelation from patchwork.models import Person from patchwork.models import Project from patchwork.models import Series @@ -174,3 +175,10 @@ class TagAdmin(admin.ModelAdmin): admin.site.register(Tag, TagAdmin) + + +class PatchRelationAdmin(admin.ModelAdmin): + model = PatchRelation + + +admin.site.register(PatchRelation, PatchRelationAdmin) diff --git a/patchwork/api/event.py b/patchwork/api/event.py index fdff6a4f..44c34520 100644 --- a/patchwork/api/event.py +++ b/patchwork/api/event.py @@ -42,6 +42,8 @@ class EventSerializer(ModelSerializer): 'current_state'], Event.CATEGORY_PATCH_DELEGATED: ['patch', 'previous_delegate', 'current_delegate'], + Event.CATEGORY_PATCH_RELATION_CHANGED: ['patch', 'previous_relation', + 'current_relation'], Event.CATEGORY_CHECK_CREATED: ['patch', 'created_check'], Event.CATEGORY_SERIES_CREATED: ['series'], Event.CATEGORY_SERIES_COMPLETED: ['series'], @@ -68,7 +70,8 @@ class EventSerializer(ModelSerializer): model = Event fields = ('id', 'category', 'project', 'date', 'actor', 'patch', 'series', 'cover', 'previous_state', 'current_state', - 'previous_delegate', 'current_delegate', 'created_check') + 'previous_delegate', 'current_delegate', 'created_check', + 'previous_relation', 'current_relation',) read_only_fields = fields versioned_fields = { '1.2': ('actor', ), diff --git a/patchwork/migrations/0040_add_related_patches.py b/patchwork/migrations/0040_add_related_patches.py new file mode 100644 index 00000000..fc2994dc --- /dev/null +++ b/patchwork/migrations/0040_add_related_patches.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('patchwork', '0039_unique_series_references'), + ] + + operations = [ + migrations.CreateModel( + name='PatchRelation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + ), + migrations.AlterField( + model_name='event', + name='category', + field=models.CharField(choices=[(b'cover-created', b'Cover Letter Created'), (b'patch-created', b'Patch Created'), (b'patch-completed', b'Patch Completed'), (b'patch-state-changed', b'Patch State Changed'), (b'patch-delegated', b'Patch Delegate Changed'), (b'patch-relation-changed', b'Patch Relation Changed'), (b'check-created', b'Check Created'), (b'series-created', b'Series Created'), (b'series-completed', b'Series Completed')], db_index=True, help_text=b'The category of the event.', max_length=25), + ), + migrations.AddField( + model_name='event', + name='current_relation', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='patchwork.PatchRelation'), + ), + migrations.AddField( + model_name='event', + name='previous_relation', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='patchwork.PatchRelation'), + ), + migrations.AddField( + model_name='patch', + name='related', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='patches', related_query_name='patch', to='patchwork.PatchRelation'), + ), + ] diff --git a/patchwork/models.py b/patchwork/models.py index be71d407..4f7186d3 100644 --- a/patchwork/models.py +++ b/patchwork/models.py @@ -447,6 +447,12 @@ class Patch(Submission): default=None, null=True, help_text='The number assigned to this patch in the series') + # related patches metadata + + related = models.ForeignKey( + 'PatchRelation', null=True, blank=True, on_delete=models.SET_NULL, + related_name='patches', related_query_name='patch') + objects = PatchManager() @staticmethod @@ -863,6 +869,19 @@ class BundlePatch(models.Model): ordering = ['order'] +@python_2_unicode_compatible +class PatchRelation(models.Model): + + def __str__(self): + patches = self.patches.all() + if not patches: + return '' + name = ', '.join(patch.name for patch in patches[:10]) + if len(name) > 60: + name = name[:60] + '...' + return name + + @python_2_unicode_compatible class Check(models.Model): @@ -928,6 +947,7 @@ class Event(models.Model): CATEGORY_PATCH_COMPLETED = 'patch-completed' CATEGORY_PATCH_STATE_CHANGED = 'patch-state-changed' CATEGORY_PATCH_DELEGATED = 'patch-delegated' + CATEGORY_PATCH_RELATION_CHANGED = 'patch-relation-changed' CATEGORY_CHECK_CREATED = 'check-created' CATEGORY_SERIES_CREATED = 'series-created' CATEGORY_SERIES_COMPLETED = 'series-completed' @@ -937,6 +957,7 @@ class Event(models.Model): (CATEGORY_PATCH_COMPLETED, 'Patch Completed'), (CATEGORY_PATCH_STATE_CHANGED, 'Patch State Changed'), (CATEGORY_PATCH_DELEGATED, 'Patch Delegate Changed'), + (CATEGORY_PATCH_RELATION_CHANGED, 'Patch Relation Changed'), (CATEGORY_CHECK_CREATED, 'Check Created'), (CATEGORY_SERIES_CREATED, 'Series Created'), (CATEGORY_SERIES_COMPLETED, 'Series Completed'), @@ -952,7 +973,7 @@ class Event(models.Model): # event metadata category = models.CharField( - max_length=20, + max_length=25, choices=CATEGORY_CHOICES, db_index=True, help_text='The category of the event.') @@ -1000,6 +1021,15 @@ class Event(models.Model): User, related_name='+', null=True, blank=True, on_delete=models.CASCADE) + # fields for 'patch-relation-changed-changed' events + + previous_relation = models.ForeignKey( + PatchRelation, related_name='+', null=True, blank=True, + on_delete=models.CASCADE) + current_relation = models.ForeignKey( + PatchRelation, related_name='+', null=True, blank=True, + on_delete=models.CASCADE) + # fields or 'patch-check-created' events created_check = models.ForeignKey( diff --git a/patchwork/signals.py b/patchwork/signals.py index 73ddfa5e..3a2f0fbd 100644 --- a/patchwork/signals.py +++ b/patchwork/signals.py @@ -134,6 +134,30 @@ def create_patch_delegated_event(sender, instance, raw, **kwargs): create_event(instance, orig_patch.delegate, instance.delegate) +@receiver(pre_save, sender=Patch) +def create_patch_relation_changed_event(sender, instance, raw, **kwargs): + + def create_event(patch, before, after): + return Event.objects.create( + category=Event.CATEGORY_PATCH_RELATION_CHANGED, + project=patch.project, + actor=getattr(patch, '_edited_by', None), + patch=patch, + previous_relation=before, + current_relation=after) + + # don't trigger for items loaded from fixtures or new items + if raw or not instance.pk: + return + + orig_patch = Patch.objects.get(pk=instance.pk) + + if orig_patch.related == instance.related: + return + + create_event(instance, orig_patch.related, instance.related) + + @receiver(pre_save, sender=Patch) def create_patch_completed_event(sender, instance, raw, **kwargs): diff --git a/patchwork/templates/patchwork/submission.html b/patchwork/templates/patchwork/submission.html index 77a2711a..978559b8 100644 --- a/patchwork/templates/patchwork/submission.html +++ b/patchwork/templates/patchwork/submission.html @@ -110,6 +110,43 @@ function toggle_div(link_id, headers_id, label_show, label_hide) {% endif %} +{% if submission.related %} + + Related + + show + + + +{% endif %}
diff --git a/patchwork/views/patch.py b/patchwork/views/patch.py index f34053ce..8202600e 100644 --- a/patchwork/views/patch.py +++ b/patchwork/views/patch.py @@ -110,12 +110,26 @@ def patch_detail(request, project_id, msgid): comments = comments.only('submitter', 'date', 'id', 'content', 'submission') + if patch.related: + related_same_project = patch.related.patches.only( + 'name', 'msgid', 'project', 'related') + # avoid a second trip out to the db for info we already have + related_different_project = [ + related_patch for related_patch in related_same_project + if related_patch.project_id != patch.project_id + ] + else: + related_same_project = [] + related_different_project = [] + context['comments'] = comments context['checks'] = patch.check_set.all().select_related('user') context['submission'] = patch context['patchform'] = form context['createbundleform'] = createbundleform context['project'] = patch.project + context['related_same_project'] = related_same_project + context['related_different_project'] = related_different_project return render(request, 'patchwork/submission.html', context)