From nobody Tue May 13 15:30:34 2025 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) client-ip=209.132.183.28; envelope-from=patchew-devel-bounces@redhat.com; helo=mx1.redhat.com; Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zoho.com: domain of redhat.com designates 209.132.183.28 as permitted sender) smtp.mailfrom=patchew-devel-bounces@redhat.com; dmarc=pass(p=none dis=none) header.from=redhat.com Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 1535054470446125.79169818842524; Thu, 23 Aug 2018 13:01:10 -0700 (PDT) Received: from smtp.corp.redhat.com (int-mx10.intmail.prod.int.phx2.redhat.com [10.5.11.25]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 1480B30820D5; Thu, 23 Aug 2018 20:01:09 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.21]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 0327E2010D11; Thu, 23 Aug 2018 20:01:09 +0000 (UTC) Received: from lists01.pubmisc.prod.ext.phx2.redhat.com (lists01.pubmisc.prod.ext.phx2.redhat.com [10.5.19.33]) by colo-mx.corp.redhat.com (Postfix) with ESMTP id E79FB4BB74; Thu, 23 Aug 2018 20:01:08 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx09.intmail.prod.int.phx2.redhat.com [10.5.11.24]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id w7NK17qI027554 for ; Thu, 23 Aug 2018 16:01:07 -0400 Received: by smtp.corp.redhat.com (Postfix) id DB9A8308BDA8; Thu, 23 Aug 2018 20:01:07 +0000 (UTC) Received: from mx1.redhat.com (ext-mx17.extmail.prod.ext.phx2.redhat.com [10.5.110.46]) by smtp.corp.redhat.com (Postfix) with ESMTPS id CE8AE308BDA0 for ; Thu, 23 Aug 2018 20:01:06 +0000 (UTC) Received: from mail-wr1-f46.google.com (mail-wr1-f46.google.com [209.85.221.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 6CEE43082E05 for ; Thu, 23 Aug 2018 20:01:04 +0000 (UTC) Received: by mail-wr1-f46.google.com with SMTP id 20-v6so5597415wrb.12 for ; Thu, 23 Aug 2018 13:01:04 -0700 (PDT) Received: from donizetti.lan (dynamic-adsl-78-12-184-244.clienti.tiscali.it. [78.12.184.244]) by smtp.gmail.com with ESMTPSA id v5-v6sm4330793wru.60.2018.08.23.13.01.01 for (version=TLS1_2 cipher=ECDHE-RSA-CHACHA20-POLY1305 bits=256/256); Thu, 23 Aug 2018 13:01:02 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=sender:from:to:subject:date:message-id:in-reply-to:references; bh=D/6kv1+QztxfG88vwAcfV14i0uvywZSFaW+zJF14Ahs=; b=jRY3XlL8+G2SJUxVKzTovQke88RL67OufTn31QxeYjjAUwyt1H+Frau7PzsfcL2EfI lAwpOcp/F636U8OULCeG/oPb66MjsN9fA36ZuXWrCTfb8Ga8DuO1v6yT28CoPN7IR/GM viM5zMAel9AXhb3W+wzjnA1akkViYLMCCM8Tjtaxfoah2Lx+Ev0STnzwnZ9vE/ne2ikS ZySyays65CrtHzsyzFScfB7mBmSn+OencRRnczP2BTl7mtQb4iMPomlEKPvEN4Q3KwxU RYWqU+3O6iHejhVm9yOIC74Mirqlp9uHFDASSsTik3ofB40KuJLcTRtFrKNHmnP/BvnW oplQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:sender:from:to:subject:date:message-id :in-reply-to:references; bh=D/6kv1+QztxfG88vwAcfV14i0uvywZSFaW+zJF14Ahs=; b=Rxvbob2bCgwBtRw82lG7J7D2Ou2Wg2WIYddtTyMws0SsopfquxwIiqLZTjfJqGQwOr GHcPjod/7G9HtGHMMNzHf/pCj6SoRyNDNPUkCBYNE3AkiEU2F2VjG1rGNMpPZF6ijx90 xTZltZoyL9ye/dcRVaalpobrPuo84DA3DEH93hYDFfQZBmvbWriK7tTMOwGiK6UchLBs JRCuv4VJhlE5yyjgxyw2mDnF6Oh/h6P8GNFsdPy7OpqW6GjCbrVxUW9CQlPwjQfRFvq0 ECMMy2YDWxwVotnYlGgfilBYB+pofFDfC6G5lu+ANA75X/CCl0ddyQS3AVpDXlH9vB6n WvUw== X-Gm-Message-State: AOUpUlHdjCzi1ym+oaKXQKUrPAmSpcrssg6JPn5smT6QlApCLox5ZS3t Catif0QUERmfaw7UrewclRUVOkcE X-Google-Smtp-Source: AA+uWPzGTch6kYXVfUQkZ0SKQbGm4SRW42BrCLDEmkvYGGaw1SUCto6tmsyLXmii29GRmaZ+LasY0g== X-Received: by 2002:adf:d08c:: with SMTP id y12-v6mr39437521wrh.152.1535054462754; Thu, 23 Aug 2018 13:01:02 -0700 (PDT) From: Paolo Bonzini To: patchew-devel@redhat.com Date: Thu, 23 Aug 2018 22:00:55 +0200 Message-Id: <20180823200055.2389-7-pbonzini@redhat.com> In-Reply-To: <20180823200055.2389-1-pbonzini@redhat.com> References: <20180823200055.2389-1-pbonzini@redhat.com> X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.46]); Thu, 23 Aug 2018 20:01:04 +0000 (UTC) X-Greylist: inspected by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.46]); Thu, 23 Aug 2018 20:01:04 +0000 (UTC) for IP:'209.85.221.46' DOMAIN:'mail-wr1-f46.google.com' HELO:'mail-wr1-f46.google.com' FROM:'paolo.bonzini@gmail.com' RCPT:'' X-RedHat-Spam-Score: 0.241 (DKIM_SIGNED, DKIM_VALID, FREEMAIL_FORGED_FROMDOMAIN, FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS, RCVD_IN_DNSWL_NONE, SPF_PASS) 209.85.221.46 mail-wr1-f46.google.com 209.85.221.46 mail-wr1-f46.google.com X-RedHat-Possible-Forgery: Paolo Bonzini X-Scanned-By: MIMEDefang 2.84 on 10.5.110.46 X-Scanned-By: MIMEDefang 2.84 on 10.5.11.24 X-loop: patchew-devel@redhat.com Subject: [Patchew-devel] [PATCH 6/6] add Review model X-BeenThere: patchew-devel@redhat.com X-Mailman-Version: 2.1.12 Precedence: junk List-Id: Patchew development and discussion list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Sender: patchew-devel-bounces@redhat.com Errors-To: patchew-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.84 on 10.5.11.25 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.47]); Thu, 23 Aug 2018 20:01:09 +0000 (UTC) X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZohoMail: RDMRC_0 RDKM_2 RSF_0 Z_629925259 SPT_0 Content-Type: text/plain; charset="utf-8" This model has a simple many-to-many relation with series, that lets logged-in users mark series as accepted or rejected, and use the status in a query. Three new search operators are added: ack (with synonyms accept and accepted), nack (with synonyms reject and rejected), review (with synonyms reviewed). Sample useful queries include the following: - "-is:merged ack:me" (what should I merge) - "to:pbonzini@redhat.com -is:merged -reviewed:me" (what should I review) - "nvme -is:merged -reviewed:fam" (what did Fam overlook :)) Signed-off-by: Paolo Bonzini --- api/migrations/0034_auto_20180822_1106.py | 32 ++++++++++ api/models.py | 7 +++ api/search.py | 28 +++++++++ mods/maintainer.py | 72 +++++++++++++++++++++-- static/css/base.css | 2 +- 5 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 api/migrations/0034_auto_20180822_1106.py diff --git a/api/migrations/0034_auto_20180822_1106.py b/api/migrations/003= 4_auto_20180822_1106.py new file mode 100644 index 0000000..d830d47 --- /dev/null +++ b/api/migrations/0034_auto_20180822_1106.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.13 on 2018-08-22 11:06 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies =3D [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('api', '0033_auto_20180803_0809'), + ] + + operations =3D [ + migrations.CreateModel( + name=3D'Review', + fields=3D[ + ('id', models.AutoField(auto_created=3DTrue, primary_key= =3DTrue, serialize=3DFalse, verbose_name=3D'ID')), + ('accept', models.BooleanField()), + ('message', models.ForeignKey(on_delete=3Ddjango.db.models= .deletion.CASCADE, to=3D'api.Message')), + ('user', models.ForeignKey(on_delete=3Ddjango.db.models.de= letion.CASCADE, to=3Dsettings.AUTH_USER_MODEL)), + ], + ), + migrations.AddField( + model_name=3D'message', + name=3D'reviews', + field=3Dmodels.ManyToManyField(blank=3DTrue, through=3D'api.Re= view', to=3Dsettings.AUTH_USER_MODEL), + ), + ] diff --git a/api/models.py b/api/models.py index eea924f..545013e 100644 --- a/api/models.py +++ b/api/models.py @@ -419,6 +419,11 @@ class MessageManager(models.Manager): def HeaderFieldModel(**args): return models.CharField(max_length=3D4096, **args) =20 +class Review(models.Model): + user =3D models.ForeignKey(User) + message =3D models.ForeignKey('Message') + accept =3D models.BooleanField() + class Message(models.Model): """ Patch email message """ =20 @@ -455,6 +460,8 @@ class Message(models.Model): # number of patches we've got if is_series_head num_patches =3D models.IntegerField(null=3DFalse, default=3D-1, blank= =3DTrue) =20 + reviews =3D models.ManyToManyField(User, blank=3DTrue, through=3DRevie= w) + objects =3D MessageManager() =20 def save_mbox(self, mbox_blob): diff --git a/api/search.py b/api/search.py index aa77f22..bcac3ac 100644 --- a/api/search.py +++ b/api/search.py @@ -104,6 +104,18 @@ Example: =20 --- =20 +### Search by review state + +Syntax: + + - accept:USERNAME or ack:USERNAME - the series was marked as accepted by = the user + - reject:USERNAME or nack:USERNAME - the series was marked as rejected by= the user + - review:USERNAME - the series was marked as accepted or rejected by the = user + +USERNAME can be "me" to identify the current user + +--- + ### Reverse condition =20 - Syntax: !TERM @@ -202,6 +214,12 @@ Search text keyword in the email message. Example: def _make_filter_result(self, term, **kwargs): return Q(results__name=3Dterm, **kwargs) | Q(results__name__starts= with=3Dterm+'.', **kwargs) =20 + def _make_filter_review(self, username, user, **kwargs): + if username =3D=3D "me": + return Q(review__user=3Duser, **kwargs) + else: + return Q(review__user__username=3Dusername, **kwargs) + def _make_filter(self, term, user): if term.startswith("age:"): cond =3D term[term.find(":") + 1:] @@ -240,6 +258,16 @@ Search text keyword in the email message. Example: return self._make_filter_result(term[8:], results__status=3DRe= sult.PENDING) elif term.startswith("running:"): return self._make_filter_result(term[8:], results__status=3DRe= sult.RUNNING) + return self._make_filter_result(cond[8:], results__status=3DRe= sult.RUNNING) + elif term.startswith("ack:") or term.startswith("accept:") or term= .startswith("accepted:"): + username =3D term[term.find(":") + 1:] + return self._make_filter_review(username, user, review__accept= =3DTrue) + elif term.startswith("nack:") or term.startswith("reject:") or ter= m.startswith("rejected:"): + username =3D term[term.find(":") + 1:] + return self._make_filter_review(username, user, review__accept= =3DFalse) + elif term.startswith("review:") or term.startswith("reviewed:"): + username =3D term[term.find(":") + 1:] + return self._make_filter_review(username, user) elif term.startswith("project:"): cond =3D term[term.find(":") + 1:] self._projects.add(cond) diff --git a/mods/maintainer.py b/mods/maintainer.py index e25d8e2..7cb23d2 100644 --- a/mods/maintainer.py +++ b/mods/maintainer.py @@ -12,13 +12,27 @@ from django.conf.urls import url from django.http import Http404, HttpResponseRedirect from django.urls import reverse from mod import PatchewModule -from api.models import Message +from api.models import Message, Review =20 class MaintainerModule(PatchewModule): """ Project maintainer related tasks """ =20 name =3D "maintainer" =20 + def _update_review_state(self, request, message_id, accept): + msg =3D Message.objects.find_series(message_id) + r =3D Review.objects.filter(user=3Drequest.user, message=3Dmsg) + r =3D r[0] if r else Review(user=3Drequest.user, message=3Dmsg) + r.accept =3D accept + r.save() + return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + + def _delete_review(self, request, message_id): + msg =3D Message.objects.find_series(message_id) + r =3D Review.objects.filter(user=3Drequest.user, message=3Dmsg) + r.delete() + return HttpResponseRedirect(request.META.get('HTTP_REFERER')) + def _update_merge_state(self, request, message_id, is_merged): s =3D Message.objects.find_series(message_id) if not s: @@ -35,6 +49,15 @@ class MaintainerModule(PatchewModule): def www_view_clear_merged(self, request, message_id): return self._update_merge_state(request, message_id, False) =20 + def www_view_mark_accepted(self, request, message_id): + return self._update_review_state(request, message_id, True) + + def www_view_mark_rejected(self, request, message_id): + return self._update_review_state(request, message_id, False) + + def www_view_clear_reviewed(self, request, message_id): + return self._delete_review(request, message_id) + def www_url_hook(self, urlpatterns): urlpatterns.append(url(r"^mark-as-merged/(?P.*)/", self.www_view_mark_as_merged, @@ -42,18 +65,59 @@ class MaintainerModule(PatchewModule): urlpatterns.append(url(r"^clear-merged/(?P.*)/", self.www_view_clear_merged, name=3D"clear-merged")) + urlpatterns.append(url(r"^mark-as-accepted/(?P.*)/", + self.www_view_mark_accepted, + name=3D"mark-as-accepted")) + urlpatterns.append(url(r"^mark-as-rejected/(?P.*)/", + self.www_view_mark_rejected, + name=3D"mark-as-rejected")) + urlpatterns.append(url(r"^clear-reviewed/(?P.*)/", + self.www_view_clear_reviewed, + name=3D"clear-reviewed")) =20 def prepare_message_hook(self, request, message, detailed): - if not detailed: + if not detailed or not request.user.is_authenticated: return - if message.is_series_head and request.user.is_authenticated: + if message.is_series_head: if message.is_merged: message.extra_ops.append({"url": reverse("clear-merged", kwargs=3D{"messag= e_id": message.message_id}), - "icon": "times", + "icon": "eraser", "title": "Clear merged state"}) else: message.extra_ops.append({"url": reverse("mark-as-merged", kwargs=3D{"messag= e_id": message.message_id}), "icon": "check", "title": "Mark series as merged"= }) + + r =3D Review.objects.filter(user=3Drequest.user, message=3Dmessage) + if not r or not r[0].accept: + if message.is_series_head: + message.extra_ops.append({"url": reverse("mark-as-accepted= ", + kwargs=3D{"messag= e_id": message.message_id}), + "icon": "check", + "title": "Mark series as accepte= d"}) + else: + message.extra_status.append({ + "icon": "fa-check", + "html": 'The series is marked for merging' + }) + + if not r or r[0].accept: + if message.is_series_head: + message.extra_ops.append({"url": reverse("mark-as-rejected= ", + kwargs=3D{"messag= e_id": message.message_id}), + "icon": "times", + "title": "Mark series as rejecte= d"}) + else: + message.extra_status.append({ + "icon": "fa-times", + "html": 'The series is marked as rejected' + }) + + print(r) + if r: + message.extra_ops.append({"url": reverse("clear-reviewed", + kwargs=3D{"message_id= ": message.message_id}), + "icon": "eraser", + "title": "Clear review state"}) diff --git a/static/css/base.css b/static/css/base.css index 7fe45e4..9e04f50 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -137,7 +137,7 @@ h1, h2, h3, .h1, .h2, .h3 { .status-content > .fa { color: #337ab7; } -.status-content > .fa-warning { +.status-content > .fa-warning, .status-content > .fa-times { color: #CC0000; } .status-content > .fa-check { --=20 2.17.1 _______________________________________________ Patchew-devel mailing list Patchew-devel@redhat.com https://www.redhat.com/mailman/listinfo/patchew-devel