From nobody Wed Apr 24 18:10:23 2024 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; 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 ARC-Seal: i=1; a=rsa-sha256; t=1555604514; cv=none; d=zoho.com; s=zohoarc; b=GS2iJQkzjtj6Asz4KAcOKn49gH7zn5X3yiP80MkzQsZgxpGOs2YJgySN9OcQelpPz02nRNH24p9sH1RgvkFFbStCuP//epggZbhJsQrzbftX1qw9C2QuifFar08hTMiDTkTSDAKD36xEtSal03EQq1VHxHFD6U2y1LIk/qRgzXw= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1555604514; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To:ARC-Authentication-Results; bh=DxOZsVwUemIJ5fHMoHKSaVGLt2jzylY4r2JGwgOkhbo=; b=kCLv4hrxis+WMjFrqjp2KkDm7xzn7lxU4UnSCxtYUlCffpCSBek5YhKgd3aM4ISyFc9vPBogYhkGyFzan0Q4/V6AdWjVnApEhmY+YnmyA+MJ1R95nqcw6r5uUk3rgMFffbdvuUhwk+wZZNqNNgqKg/XkTUrkonWSQFkrZZ1cq6Q= ARC-Authentication-Results: i=1; mx.zoho.com; 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 header.from= (p=none dis=none) header.from= Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 1555604514381214.00796736551013; Thu, 18 Apr 2019 09:21:54 -0700 (PDT) Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 31C1170D6B; Thu, 18 Apr 2019 16:21:53 +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 2749560851; Thu, 18 Apr 2019 16:21:53 +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 1E0514EA71; Thu, 18 Apr 2019 16:21:53 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id x3IGLpt5009147 for ; Thu, 18 Apr 2019 12:21:51 -0400 Received: by smtp.corp.redhat.com (Postfix) id 88F7A60BEC; Thu, 18 Apr 2019 16:21:51 +0000 (UTC) Received: from donizetti.redhat.com (ovpn-112-46.ams2.redhat.com [10.36.112.46]) by smtp.corp.redhat.com (Postfix) with ESMTP id DD74360BE5 for ; Thu, 18 Apr 2019 16:21:50 +0000 (UTC) From: Paolo Bonzini To: patchew-devel@redhat.com Date: Thu, 18 Apr 2019 18:21:37 +0200 Message-Id: <20190418162146.5695-2-pbonzini@redhat.com> In-Reply-To: <20190418162146.5695-1-pbonzini@redhat.com> References: <20190418162146.5695-1-pbonzini@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-loop: patchew-devel@redhat.com Subject: [Patchew-devel] [PATCH 01/10] api, patchew-cli: remove commands to directly access properties 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: , Content-Transfer-Encoding: quoted-printable Sender: patchew-devel-bounces@redhat.com Errors-To: patchew-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.28]); Thu, 18 Apr 2019 16:21:53 +0000 (UTC) Content-Type: text/plain; charset="utf-8" set-properties was completely unused; get-project-properties was not needed for the Javascript interface, only for the "project property" command. The Django admin is enough if one needs that kind of fine-grained access. --- api/views.py | 23 -------------------- patchew-cli | 61 ---------------------------------------------------- 2 files changed, 84 deletions(-) diff --git a/api/views.py b/api/views.py index 48a3587..d323ba3 100644 --- a/api/views.py +++ b/api/views.py @@ -111,16 +111,6 @@ class AddProjectView(APILoginRequiredView): p.save() =20 =20 -class GetProjectPropertiesView(APILoginRequiredView): - name =3D "get-project-properties" - - def handle(self, request, project): - po =3D Project.objects.get(name=3Dproject) - if not po.maintained_by(request.user): - raise PermissionDenied("Access denied to this project") - return po.get_properties() - - class UpdateProjectHeadView(APILoginRequiredView): name =3D "update-project-head" allowed_groups =3D ["importers"] @@ -135,19 +125,6 @@ class UpdateProjectHeadView(APILoginRequiredView): return ret =20 =20 -class SetPropertyView(APILoginRequiredView): - name =3D "set-properties" - allowed_groups =3D ["importers"] - - def handle(self, request, project, message_id, properties): - mo =3D Message.objects.filter(project__name=3Dproject, - message_id=3Dmessage_id).first() - if not mo: - raise Http404("Message not found") - for k, v in properties.items(): - mo.set_property(k, v) - - class SetProjectPropertiesView(APILoginRequiredView): name =3D "set-project-properties" allowed_groups =3D ["maintainers"] diff --git a/patchew-cli b/patchew-cli index 7fa7d49..efc6f13 100755 --- a/patchew-cli +++ b/patchew-cli @@ -346,42 +346,10 @@ class ProjectCommand(SubCommand): finally: shutil.rmtree(wd) =20 - def project_property(self, argv): - parser =3D argparse.ArgumentParser() - parser.add_argument("name", help=3D"Name of the project") - parser.add_argument("prop", nargs=3D"?", help=3D"Name of the prope= rty") - parser.add_argument("--delete", "-d", action=3D"store_true", - help=3D"""delete the property with the given n= ame. - Must give a property name""") - parser.add_argument("value", nargs=3D"?", help=3D"Value of the pro= perty to set") - args =3D parser.parse_args(argv) - if not args.value and not args.delete: - # Get property and print them or the specified one - r =3D self.api_do("get-project-properties", - project=3Dargs.name) - if not args.prop: - for k, v in iter(r.items()) if r else []: - print(k, v) - else: - if r and args.prop in r: - print(args.prop, r[args.prop]) - else: - print("Property Not found:", args.prop) - if r and list(r.keys()): - print("There are:", ", ".join(list(r.keys()))) - else: - if args.delete: - args.value =3D None - # Set property - self.api_do("set-project-properties", project=3Dargs.name, - properties=3D{args.prop: args.value}) - def do(self, args, argv): if argv: if argv[0] =3D=3D "add": return self.add_project(argv[1:]) - elif argv[0] =3D=3D "property": - return self.project_property(argv[1:]) elif argv[0] =3D=3D "info": return self.show_project(argv[1:]) elif argv[0] =3D=3D "update": @@ -420,35 +388,6 @@ class UntestCommand(SubCommand): self.api_do("untest", terms=3Dargs.term) return 0 =20 -class SetPropertyCommand(SubCommand): - name =3D "set-property" - want_argv =3D True - - def arguments(self, parser): - parser.add_argument("--project", "-p", required=3DTrue, - help=3D"Project name") - parser.add_argument("--message-id", "-m", required=3DTrue, - help=3D"Project name") - parser.add_argument("--file", action=3D"store_true", - help=3D"Read property values from given file") - parser.add_argument("--json", action=3D"store_true", - help=3D"Read property values as json") - - def do(self, args, argv): - if not argv: - return 0 - if len(argv) % 2: - print("Name and value unpaired:", argv[-1]) - props =3D dict(list(zip(argv[::2], argv[1::2]))) - if args.file: - props =3D dict([(k, open(v, "r").read()) for k, v in props.ite= ms()]) - if args.json: - props =3D dict([(k, json.loads(v)) for k, v in props.items()]) - self.api_do("set-properties", project=3Dargs.project, - message_id=3Dargs.message_id, - properties=3Dprops) - return 0 - class TesterCommand(SubCommand): name =3D "tester" want_argv =3D True --=20 2.21.0 _______________________________________________ Patchew-devel mailing list Patchew-devel@redhat.com https://www.redhat.com/mailman/listinfo/patchew-devel From nobody Wed Apr 24 18:10:23 2024 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; 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 ARC-Seal: i=1; a=rsa-sha256; t=1555604514; cv=none; d=zoho.com; s=zohoarc; b=lGQI9qZ3AmagRWTy7yHuS2ggN0fIelMg2HCnwSHy1QnbH3LKDb7HzLUOPWy9YHTm1O9QmDTAuXw/X/cqkHZvYuS5UV0ai+6F3XT5+VC0CovlyldiXr4EBKjwkuGKgCsM18rSZaTrJPrXCAbvgDcVoz1vTKF5PxSFdmyZHB+OSHg= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1555604514; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To:ARC-Authentication-Results; bh=y74TGQuCYnRafn+ccu3Lm9oWR4AyC7ZFHVcIA9UW5eo=; b=fbckT4gjDd/p/aWRDHzV+BFVLl3dk8IHfFjLEfXAsJvUSt1RgPUP+e4FgTWKIqJkOXDeGOt6h+c7rnLJxqWdU9mvE5eI6aYWIdjO/xKY4wUR6wpXE3dysAo13nHQlcZiv5zRkb71M7UaS8aiUzt4QhfaJTQZ4mJtNvsTixQ04EE= ARC-Authentication-Results: i=1; mx.zoho.com; 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 header.from= (p=none dis=none) header.from= Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 1555604514501579.1168632792171; Thu, 18 Apr 2019 09:21:54 -0700 (PDT) Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 4CDBD70D6D; Thu, 18 Apr 2019 16:21:53 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 41ED15D704; Thu, 18 Apr 2019 16:21:53 +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 37AF3181AC45; Thu, 18 Apr 2019 16:21:53 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id x3IGLqSA009156 for ; Thu, 18 Apr 2019 12:21:52 -0400 Received: by smtp.corp.redhat.com (Postfix) id B575F60BEC; Thu, 18 Apr 2019 16:21:52 +0000 (UTC) Received: from donizetti.redhat.com (ovpn-112-46.ams2.redhat.com [10.36.112.46]) by smtp.corp.redhat.com (Postfix) with ESMTP id 0BDE560BE5 for ; Thu, 18 Apr 2019 16:21:51 +0000 (UTC) From: Paolo Bonzini To: patchew-devel@redhat.com Date: Thu, 18 Apr 2019 18:21:38 +0200 Message-Id: <20190418162146.5695-3-pbonzini@redhat.com> In-Reply-To: <20190418162146.5695-1-pbonzini@redhat.com> References: <20190418162146.5695-1-pbonzini@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-loop: patchew-devel@redhat.com Subject: [Patchew-devel] [PATCH 02/10] git: do not return properties from applier-get 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: , Content-Transfer-Encoding: quoted-printable Sender: patchew-devel-bounces@redhat.com Errors-To: patchew-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.15 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.28]); Thu, 18 Apr 2019 16:21:53 +0000 (UTC) Content-Type: text/plain; charset="utf-8" Properties that are of interest to the applier are already returned directly in the applier-get reply; the applier is only using properties to retrieve tags from old servers that do not have commit 32bddb4 ("models: convert tags from property to field", 2018-10-30). It is not needed anymore, remove it to hide properties from the API. --- api/views.py | 14 ++++---------- patchew-cli | 4 +--- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/api/views.py b/api/views.py index d323ba3..4ac5636 100644 --- a/api/views.py +++ b/api/views.py @@ -137,14 +137,6 @@ class SetProjectPropertiesView(APILoginRequiredView): po.set_property(k, v) =20 =20 -def get_properties(m): - r =3D m.get_properties() - # for compatibility with patchew-cli's applier mode - if m.tags: - r['tags'] =3D m.tags - return r - - class DeleteProjectPropertiesByPrefixView(APILoginRequiredView): name =3D "delete-project-properties-by-prefix" allowed_groups =3D ["maintainers"] @@ -161,7 +153,8 @@ def prepare_patch(p): r =3D {"subject": p.subject, "message-id": p.message_id, "mbox": p.get_mbox(), - "properties": get_properties(p) + # For backwards compatibility with old clients + "properties": {} } return r =20 @@ -181,7 +174,8 @@ def prepare_series(request, s, fields=3DNone): if want_field("patches"): r["patches"] =3D [prepare_patch(x) for x in s.get_patches()] if want_field("properties"): - r["properties"] =3D get_properties(s) + # For backwards compatibility with old clients + r["properties"] =3D {} if want_field("tags"): r["tags"] =3D s.tags if want_field("is_complete"): diff --git a/patchew-cli b/patchew-cli index efc6f13..bfff69c 100755 --- a/patchew-cli +++ b/patchew-cli @@ -681,9 +681,7 @@ class ApplyCommand(SubCommand): subprocess.check_output(["git", "log", "-n", "1", "--format=3D%b"], cwd=3Drepo)= \ .decode('utf-8').splitlines() - # old servers do not have "tags" directly in the response - for t in set(p["properties"].get("tags", []) + p.get("tags", [= ]) + \ - s["properties"].get("tags", []) + s.get("tags", [= ])): + for t in set(p.get("tags", []) + s.get("tags", [])): if t in commit_message_lines: continue filter_cmd +=3D "echo '%s';" % t --=20 2.21.0 _______________________________________________ Patchew-devel mailing list Patchew-devel@redhat.com https://www.redhat.com/mailman/listinfo/patchew-devel From nobody Wed Apr 24 18:10:23 2024 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; 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 ARC-Seal: i=1; a=rsa-sha256; t=1555604515; cv=none; d=zoho.com; s=zohoarc; b=nE/+WBAakpEMP1Cbl9W4IhrZAobI7dbQg1ZsdpWD4myns0J0y3jwyB5VxE6CmMesc4ot4mZzgitUw6QMoe5NDsXAZdNs1IF0H/AfYewC0s5zziHnkZ2/r8ES7tdwsYkUyWcCDYRPpjpRnF4jO1o012PefL9rrnsBcPlQVJg/3mw= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1555604515; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To:ARC-Authentication-Results; bh=ZmMg0rxdeM4ke0tyP9oEyMSeEQMSOmML1Qqit70siBk=; b=N9av8Uv8Edy7FQoKHZ9OToxHzTCgsO006kFBKNV5yC3zK0L/hdx1hZ65FJDbfpfU8x5zbRGlIsjXExZoFD5v7vE1T5DoXgzy75a2LsgB7W9dkERB3sLitm6qytmnHQB3hO+sEoD+d0cFSewYGmeWWj7GDpM6FTe6gkwTPiUb0+E= ARC-Authentication-Results: i=1; mx.zoho.com; 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 header.from= (p=none dis=none) header.from= Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 1555604515825481.9875044095205; Thu, 18 Apr 2019 09:21:55 -0700 (PDT) Received: from smtp.corp.redhat.com (int-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.11]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id A1E7830B4A68; Thu, 18 Apr 2019 16:21:54 +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 943CE600C1; Thu, 18 Apr 2019 16:21:54 +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 8AD884EA71; Thu, 18 Apr 2019 16:21:54 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id x3IGLrdT009166 for ; Thu, 18 Apr 2019 12:21:53 -0400 Received: by smtp.corp.redhat.com (Postfix) id CEAC760BFC; Thu, 18 Apr 2019 16:21:53 +0000 (UTC) Received: from donizetti.redhat.com (ovpn-112-46.ams2.redhat.com [10.36.112.46]) by smtp.corp.redhat.com (Postfix) with ESMTP id 35D9F60BE5 for ; Thu, 18 Apr 2019 16:21:52 +0000 (UTC) From: Paolo Bonzini To: patchew-devel@redhat.com Date: Thu, 18 Apr 2019 18:21:39 +0200 Message-Id: <20190418162146.5695-4-pbonzini@redhat.com> In-Reply-To: <20190418162146.5695-1-pbonzini@redhat.com> References: <20190418162146.5695-1-pbonzini@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-loop: patchew-devel@redhat.com Subject: [Patchew-devel] [PATCH 03/10] api: do not blindly return all properties from get-projects 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: , Content-Transfer-Encoding: quoted-printable Sender: patchew-devel-bounces@redhat.com Errors-To: patchew-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.11 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.49]); Thu, 18 Apr 2019 16:21:54 +0000 (UTC) Content-Type: text/plain; charset="utf-8" Only return the two properties that patchew-cli actually needs, through a new hook. --- api/views.py | 6 +++++- mods/git.py | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/api/views.py b/api/views.py index 4ac5636..887f9ed 100644 --- a/api/views.py +++ b/api/views.py @@ -18,6 +18,7 @@ import json from .search import SearchEngine from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator +from mod import dispatch_module_hook =20 =20 class APIView(View): @@ -83,8 +84,11 @@ def prepare_project(p): "url": p.url, "git": p.git, "description": p.description, - "properties": p.get_properties(), + "properties": {}, } + dispatch_module_hook("get_projects_prepare_hook", project=3Dp, + response=3Dret['properties']) + return ret =20 =20 diff --git a/mods/git.py b/mods/git.py index d0b1f87..89e711d 100644 --- a/mods/git.py +++ b/mods/git.py @@ -128,6 +128,10 @@ class GitModule(PatchewModule): def rest_series_fields_hook(self, request, fields, detailed): fields['based_on'] =3D PluginMethodField(obj=3Dself, required=3DFa= lse) =20 + def get_projects_prepare_hook(self, project, response): + response["git.head"] =3D project.get_property("git.head") + response["git.push_to"] =3D project.get_property("git.push_to") + def prepare_message_hook(self, request, message, detailed): if not message.is_series_head: return --=20 2.21.0 _______________________________________________ Patchew-devel mailing list Patchew-devel@redhat.com https://www.redhat.com/mailman/listinfo/patchew-devel From nobody Wed Apr 24 18:10:23 2024 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; 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 ARC-Seal: i=1; a=rsa-sha256; t=1555604517; cv=none; d=zoho.com; s=zohoarc; b=CNkG7vg4mbryryifrQ4IdYsNXOUrRK3SnTzJtHPkjR54q9i9iYxnuJlaMM3d9lafZPgMKGETx5FZZixz8Z4LxEznURQDKdJJi3bARf6wMi0x8gqVnQHBXQ2toamqiGyepYdAo0QG8sMgd27rnXd8+05biaBFy7213EyDPJROeS0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1555604517; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To:ARC-Authentication-Results; bh=Cjx5BnOXbKWs9dSRs2cYRWouHjwQFsZm13iJdzuOmYg=; b=BILhqPu6cxQWItBODHcVZpV7ZuateuPm87810FVUs7EUKbkADmknED0R8EmJw7ehqgZwroaB0g/IRVk71uyzbfbiKMMVuR2L2Knl7vTQYPkhzJom65JuVH1XPGnow1jUDRzb458p8GZf9fDhawMT6JNwr5Y2uvK3qUoqTO8XiUo= ARC-Authentication-Results: i=1; mx.zoho.com; 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 header.from= (p=none dis=none) header.from= Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 155560451726833.08123041885085; Thu, 18 Apr 2019 09:21:57 -0700 (PDT) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 0385A307CB28; Thu, 18 Apr 2019 16:21:56 +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 ED89B60BEC; Thu, 18 Apr 2019 16:21:55 +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 E1F1F4EA71; Thu, 18 Apr 2019 16:21:55 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id x3IGLtkg009177 for ; Thu, 18 Apr 2019 12:21:55 -0400 Received: by smtp.corp.redhat.com (Postfix) id 02A8260BEC; Thu, 18 Apr 2019 16:21:55 +0000 (UTC) Received: from donizetti.redhat.com (ovpn-112-46.ams2.redhat.com [10.36.112.46]) by smtp.corp.redhat.com (Postfix) with ESMTP id 4E02260BE5 for ; Thu, 18 Apr 2019 16:21:54 +0000 (UTC) From: Paolo Bonzini To: patchew-devel@redhat.com Date: Thu, 18 Apr 2019 18:21:40 +0200 Message-Id: <20190418162146.5695-5-pbonzini@redhat.com> In-Reply-To: <20190418162146.5695-1-pbonzini@redhat.com> References: <20190418162146.5695-1-pbonzini@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-loop: patchew-devel@redhat.com Subject: [Patchew-devel] [PATCH 04/10] models: introduce flags into Messages 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: , Content-Transfer-Encoding: quoted-printable Sender: patchew-devel-bounces@redhat.com Errors-To: patchew-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.42]); Thu, 18 Apr 2019 16:21:56 +0000 (UTC) Content-Type: text/plain; charset="utf-8" Flags are used as a replacement for Boolean properties that are used by search queries. They can be searched efficiently using a trigram index. Unlike properties, flags are general and could be set by any plugin. For example, a GitHub integration webhook could set REVIEWED, OBSOLETE and TESTED based on changes made to a GitHub pull request. --- api/migrations/0046_message_flags.py | 20 +++++++++ .../0047_message_flags_postgres_fts.py | 22 ++++++++++ api/migrations/0048_populate_message_flags.py | 42 +++++++++++++++++++ api/models.py | 13 ++++++ api/search.py | 13 +++--- mods/tags.py | 5 ++- mods/testing.py | 22 +++++----- tests/test_testing.py | 9 ++-- 8 files changed, 124 insertions(+), 22 deletions(-) create mode 100644 api/migrations/0046_message_flags.py create mode 100644 api/migrations/0047_message_flags_postgres_fts.py create mode 100644 api/migrations/0048_populate_message_flags.py diff --git a/api/migrations/0046_message_flags.py b/api/migrations/0046_mes= sage_flags.py new file mode 100644 index 0000000..0db9dac --- /dev/null +++ b/api/migrations/0046_message_flags.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-04-18 14:30 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies =3D [ + ('api', '0045_message_maintainers'), + ] + + operations =3D [ + migrations.AddField( + model_name=3D'message', + name=3D'flags', + field=3Dmodels.CharField(blank=3DTrue, default=3D'', max_lengt= h=3D64), + ), + ] diff --git a/api/migrations/0047_message_flags_postgres_fts.py b/api/migrat= ions/0047_message_flags_postgres_fts.py new file mode 100644 index 0000000..bdfc84e --- /dev/null +++ b/api/migrations/0047_message_flags_postgres_fts.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.16 on 2018-11-07 15:28 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +from django.contrib.postgres.operations import TrigramExtension + +from api.migrations import PostgresOnlyMigration + + +class Migration(PostgresOnlyMigration): + + dependencies =3D [ + ('api', '0046_message_flags'), + ] + + operations =3D [ + TrigramExtension(), + migrations.RunSQL("create index api_message_flags_gin on api_messa= ge using gin(flags gin_trgm_ops);", + "drop index api_message_flags_gin"), + ] diff --git a/api/migrations/0048_populate_message_flags.py b/api/migrations= /0048_populate_message_flags.py new file mode 100644 index 0000000..835e220 --- /dev/null +++ b/api/migrations/0048_populate_message_flags.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations + +from api.search import FLAG_TESTED, FLAG_REVIEWED, FLAG_OBSOLETE + +def property_to_flags(apps, schema_editor): + MessageProperty =3D apps.get_model('api', 'MessageProperty') + for p in MessageProperty.objects.filter(name=3D'obsoleted-by'): + p.message.flags +=3D FLAG_OBSOLETE + p.message.save() + for p in MessageProperty.objects.filter(name=3D'reviewed'): + p.message.flags +=3D FLAG_REVIEWED + p.message.save() + for p in MessageProperty.objects.filter(name=3D'testing.done'): + p.message.flags +=3D FLAG_TESTED + p.message.save() + MessageProperty.objects.filter(name=3D'reviewed').delete() + MessageProperty.objects.filter(name=3D'testing.done').delete() + +def flags_to_property(apps, schema_editor): + Message =3D apps.get_model('api', 'Message') + for m in Message.objects.exclude(flags=3D''): + if '[reviewed]' in m.flags: + new_prop =3D MessageProperty(message=3Dp.message, name=3D'revi= ewed', value=3DTrue) + new_prop.save() + if FLAG_TESTED in m.flags: + new_prop =3D MessageProperty(message=3Dp.message, name=3D'test= ing.done', value=3DTrue) + new_prop.save() + +class Migration(migrations.Migration): + + dependencies =3D [ + ('api', '0047_message_flags_postgres_fts'), + ] + + operations =3D [ + migrations.RunPython(property_to_flags, + reverse_code=3Dflags_to_property), + ] diff --git a/api/models.py b/api/models.py index d2dafde..3176559 100644 --- a/api/models.py +++ b/api/models.py @@ -508,6 +508,7 @@ class Message(models.Model): objects =3D MessageManager() =20 maintainers =3D jsonfield.JSONField(blank=3DTrue, default=3D[]) + flags =3D models.CharField(max_length=3D64, blank=3DTrue, default=3D"") =20 def save_mbox(self, mbox_blob): save_blob(mbox_blob, self.message_id) @@ -596,6 +597,18 @@ class Message(models.Model): self.refresh_num_patches() return self.num_patches =20 + def add_flag(self, flag): + assert flag[0] =3D=3D '[' and flag[-1] =3D=3D ']' + if not flag in self.flags: + self.flags +=3D flag + self.save() + + def remove_flag(self, flag): + assert flag[0] =3D=3D '[' and flag[-1] =3D=3D ']' + if flag in self.flags: + self.flags =3D self.flags.replace(flag, '') + self.save() + def get_property(self, prop, default=3DNone): return self.get_properties().get(prop, default) =20 diff --git a/api/search.py b/api/search.py index baf6893..1c5aaaf 100644 --- a/api/search.py +++ b/api/search.py @@ -18,6 +18,10 @@ from django.contrib.postgres.search import SearchQuery, = SearchVector, SearchVect from django.db.models import Lookup from django.db.models.fields import Field =20 +# These are used by migrations. Do not change them! +FLAG_REVIEWED =3D '[reviewed]' +FLAG_OBSOLETE =3D '[obsolete]' +FLAG_TESTED =3D '[tested]' =20 @Field.register_lookup class NotEqual(Lookup): @@ -254,16 +258,13 @@ Search text keyword in the email message. Example: self._add_to_keywords('PULL') return Q(subject__contains=3D'[PULL') | Q(subject__contains=3D= '[GIT PULL') elif cond =3D=3D "reviewed": - return self._make_filter_subquery(MessageProperty, Q(name=3D"r= eviewed", value=3DTrue)) + return Q(flags__contains=3DFLAG_REVIEWED) elif cond in ("obsoleted", "old"): - return self._make_filter_subquery( - MessageProperty, - Q(name=3D"obsoleted-by", value__isnull=3DFalse) & ~Q(name= =3D"obsoleted-by", value__iexact=3D'') - ) + return Q(flags__contains=3DFLAG_OBSOLETE) elif cond =3D=3D "applied": return self._make_filter_subquery(MessageResult, Q(name=3D"git= ", status=3DResult.SUCCESS)) elif cond =3D=3D "tested": - return self._make_filter_subquery(MessageProperty, Q(name=3D"t= esting.done", value=3DTrue)) + return Q(flags__contains=3DFLAG_TESTED) elif cond =3D=3D "merged": return Q(is_merged=3DTrue) return None diff --git a/mods/tags.py b/mods/tags.py index 02c493e..6b5bcc9 100644 --- a/mods/tags.py +++ b/mods/tags.py @@ -13,6 +13,7 @@ from mbox import addr_db_to_rest, parse_address from event import register_handler, emit_event, declare_event from api.models import Message from api.rest import PluginMethodField +from api.search import FLAG_OBSOLETE, FLAG_REVIEWED import rest_framework =20 REV_BY_PREFIX =3D "Reviewed-by:" @@ -73,8 +74,10 @@ series cover letter, patch mail body and their replies. return m1.version > m2.version and m1.date >=3D m2.date for m in series.get_alternative_revisions(): if newer_than(m, series): + series.add_flag(FLAG_OBSOLETE) series.set_property("obsoleted-by", m.message_id) elif newer_than(series, m): + m.add_flag(FLAG_OBSOLETE) m.set_property("obsoleted-by", series.message_id) =20 updated =3D self.update_tags(series) @@ -100,7 +103,7 @@ series cover letter, patch mail body and their replies. series_reviewers =3D _find_reviewers(series) reviewers =3D reviewers.union(series_reviewers) if num_reviewed =3D=3D series.get_num()[1] or series_reviewers: - series.set_property("reviewed", True) + series.add_flag(FLAG_REVIEWED) series.set_property("reviewers", list(reviewers)) if updated: emit_event("TagsUpdate", series=3Dseries) diff --git a/mods/testing.py b/mods/testing.py index e390e30..6a61507 100644 --- a/mods/testing.py +++ b/mods/testing.py @@ -22,7 +22,7 @@ from api.views import APILoginRequiredView from api.models import (Message, MessageProperty, MessageResult, Project, ProjectResult, Result) from api.rest import PluginMethodField, reverse_detail -from api.search import SearchEngine +from api.search import SearchEngine, FLAG_TESTED from event import emit_event, declare_event, register_handler from patchew.logviewer import LogView from schema import * @@ -137,9 +137,9 @@ class TestingModule(PatchewModule): _instance.tester_check_in(po, result.data['tester']) if not self.get_testing_results(obj, status__in=3D(Result.PENDING, Result.RUNNING)).= exists(): - obj.set_property("testing.done", True) obj.set_property("testing.tested-head", result.data["head"= ]) if isinstance(obj, Message): + obj.add_flag(FLAG_TESTED) obj.set_property("testing.tested-base", self.get_msg_base_tags(obj)) if isinstance(obj, Project): @@ -187,10 +187,11 @@ class TestingModule(PatchewModule): for tn in all_tests: if not tn in done_tests: obj.create_result(name=3D'testing.' + tn, status=3DRes= ult.PENDING).save() - if len(done_tests) < len(all_tests): - obj.set_property("testing.done", None) - return - obj.set_property("testing.done", True) + if isinstance(obj, Message): + if len(all_tests) and len(done_tests) =3D=3D len(all_tests): + obj.add_flag(FLAG_TESTED) + else: + obj.remove_flag(FLAG_TESTED) =20 def project_recalc_pending_tests(self, project): self.recalc_pending_tests(project) @@ -203,10 +204,9 @@ class TestingModule(PatchewModule): self.recalc_pending_tests(obj) =20 def clear_and_start_testing(self, obj, test=3D""): - for k in list(obj.get_properties().keys()): - if k =3D=3D "testing.done" or \ - k =3D=3D "testing.tested-head": - obj.set_property(k, None) + obj.set_property("testing.tested-head", None) + if isinstance(obj, Message): + obj.remove_flag(FLAG_TESTED) if test: r =3D self.get_testing_result(obj, test) if r: @@ -352,7 +352,7 @@ class TestingModule(PatchewModule): "type": "danger", "char": "T", }) - elif message.get_property("testing.done"): + elif FLAG_TESTED in message.flags: message.status_tags.append({ "title": "Testing passed", "url": reverse("series_detail", diff --git a/tests/test_testing.py b/tests/test_testing.py index a5c22e1..630e7f9 100755 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -12,6 +12,7 @@ import abc import subprocess =20 from api.models import Message, Result +from api.search import FLAG_TESTED =20 from .patchewtest import PatchewTestCase, main =20 @@ -56,7 +57,6 @@ class TestingTestCase(PatchewTestCase, metaclass=3Dabc.AB= CMeta): if 'status' not in kwargs: kwargs['status'] =3D Result.SUCCESS self.modify_test_result(obj, **kwargs) - obj.set_property("testing.done", True) =20 def do_testing_report(self, **report): self.api_login() @@ -167,6 +167,7 @@ class MessageTestingTest(TestingTestCase): =20 def do_testing_done(self, **kwargs): self._do_testing_done(self.msg, **kwargs) + self.msg.add_flag(FLAG_TESTED) =20 def do_testing_report(self, **report): r =3D super(MessageTestingTest, self).do_testing_report(**report) @@ -353,7 +354,7 @@ class TestingResetTest(PatchewTestCase): "testing.a": Result.SUCCESS, "testing.b": Result.SUCCESS, "testing.c": Result.FAILURE}) - self.assertTrue(msg.get_property("testing.done")) + self.assertIn(FLAG_TESTED, msg.flags) =20 self.api_login() self.client.post('/login/', {'username': self.user, 'password': se= lf.password}) @@ -364,7 +365,7 @@ class TestingResetTest(PatchewTestCase): "testing.a": Result.PENDING, "testing.b": Result.SUCCESS, "testing.c": Result.FAILURE}) - self.assertFalse(msg.get_property("testing.done")) + self.assertNotIn(FLAG_TESTED, msg.flags) =20 self.client.get('/testing-reset/%s/?type=3Dmessage&test=3Db' % msg= .message_id) self.client.get('/testing-reset/%s/?type=3Dmessage&test=3Dc' % msg= .message_id) @@ -372,7 +373,7 @@ class TestingResetTest(PatchewTestCase): "testing.a": Result.PENDING, "testing.b": Result.PENDING, "testing.c": Result.PENDING}) - self.assertFalse(msg.get_property("testing.done")) + self.assertNotIn(FLAG_TESTED, msg.flags) =20 =20 class TestingDisableTest(PatchewTestCase): --=20 2.21.0 _______________________________________________ Patchew-devel mailing list Patchew-devel@redhat.com https://www.redhat.com/mailman/listinfo/patchew-devel From nobody Wed Apr 24 18:10:23 2024 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; 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 ARC-Seal: i=1; a=rsa-sha256; t=1555604518; cv=none; d=zoho.com; s=zohoarc; b=LmzRDpoucNGpnM/wHw2I3dhIW/PpZKe9CbE2zMbgAY16Di0Y8opN8KmxJ00hGevgN64d2HXuslukan/334Wesvh8bpTRKktR3nGlVdOtNg7JF7z8rdpJ5zGhgsONaC/hUPvxe+uc8yauWIeSzYnJIkIyloAf8dBvfWSpHeFwUDg= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1555604518; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To:ARC-Authentication-Results; bh=f0C/J3qNBqMt3lr+vdvFGk+Na8UR/G4M9jJsJN3Whek=; b=Zyo/lasIBR69G+UsDDyq4XGMnUTL1uuh9+fBZYJg4YgjCT4NyVDTUG45/1LWeqdj1KSmcGpf5hFQp4OXgZTwwygrLZDuAV3DLtzv2M+dP3bZjB2txoC6Hy6j/YdPtfkdvD/XJ9eIEoTGsIodZHyDkgzEuvQiIhYu6TDRrBLFNz4= ARC-Authentication-Results: i=1; mx.zoho.com; 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 header.from= (p=none dis=none) header.from= Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 1555604518832863.5014424545432; Thu, 18 Apr 2019 09:21:58 -0700 (PDT) Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 8EB4CF74A7; Thu, 18 Apr 2019 16:21:57 +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 836CD5D9C5; Thu, 18 Apr 2019 16:21:57 +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 799024EA71; Thu, 18 Apr 2019 16:21:57 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id x3IGLut3009187 for ; Thu, 18 Apr 2019 12:21:56 -0400 Received: by smtp.corp.redhat.com (Postfix) id 497F260BEC; Thu, 18 Apr 2019 16:21:56 +0000 (UTC) Received: from donizetti.redhat.com (ovpn-112-46.ams2.redhat.com [10.36.112.46]) by smtp.corp.redhat.com (Postfix) with ESMTP id 7F12360BE5 for ; Thu, 18 Apr 2019 16:21:55 +0000 (UTC) From: Paolo Bonzini To: patchew-devel@redhat.com Date: Thu, 18 Apr 2019 18:21:41 +0200 Message-Id: <20190418162146.5695-6-pbonzini@redhat.com> In-Reply-To: <20190418162146.5695-1-pbonzini@redhat.com> References: <20190418162146.5695-1-pbonzini@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-loop: patchew-devel@redhat.com Subject: [Patchew-devel] [PATCH 05/10] mods: rename project_property_schema to project_config_schema 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: , Content-Transfer-Encoding: quoted-printable Sender: patchew-devel-bounces@redhat.com Errors-To: patchew-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.38]); Thu, 18 Apr 2019 16:21:57 +0000 (UTC) Content-Type: text/plain; charset="utf-8" We would like to split plugin properties from configurations, and place them in two separate fields of the project. As a start, do not use "property" when referring to the module schemas. The fact that configuration is stored as a property is (more or less) an implementation detail. --- mod.py | 8 ++++---- mods/email.py | 2 +- mods/git.py | 2 +- mods/testing.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mod.py b/mod.py index 696dda1..319d4d3 100644 --- a/mod.py +++ b/mod.py @@ -21,7 +21,7 @@ class PatchewModule(object): """ Module base class """ name =3D None # The name of the module, must be unique default_config =3D "" # The default config string - project_property_schema =3D None + project_config_schema =3D None =20 def get_model(self): # ALways read from DB to accept configuration update in-flight @@ -145,9 +145,9 @@ class PatchewModule(object): assert False =20 def build_config_html(self, request, project): - assert not isinstance(self.project_property_schema, StringSchema) - assert not isinstance(self.project_property_schema, IntegerSchema) - scm =3D self.project_property_schema + assert not isinstance(self.project_config_schema, StringSchema) + assert not isinstance(self.project_config_schema, IntegerSchema) + scm =3D self.project_config_schema tmpl =3D self._build_one(request, project, scm.name + ".", scm) tmpl +=3D self._render_template(request, project, TMPL_END) return tmpl diff --git a/mods/email.py b/mods/email.py index 411bc4b..14553f0 100644 --- a/mods/email.py +++ b/mods/email.py @@ -88,7 +88,7 @@ Email information is configured in "INI" style: required=3DTrue), ]) =20 - project_property_schema =3D \ + project_config_schema =3D \ ArraySchema("email", desc=3D"Configuration for email module", members=3D[ MapSchema("notifications", "Email notifications", diff --git a/mods/git.py b/mods/git.py index 89e711d..110f261 100644 --- a/mods/git.py +++ b/mods/git.py @@ -63,7 +63,7 @@ class GitModule(PatchewModule): allowed_groups =3D ('importers', ) result_data_serializer_class =3D ResultDataSerializer =20 - project_property_schema =3D \ + project_config_schema =3D \ ArraySchema("git", desc=3D"Configuration for git module", members=3D[ StringSchema("push_to", "Push remote", diff --git a/mods/testing.py b/mods/testing.py index 6a61507..9df4ccd 100644 --- a/mods/testing.py +++ b/mods/testing.py @@ -91,7 +91,7 @@ class TestingModule(PatchewModule): required=3DTrue), ]) =20 - project_property_schema =3D \ + project_config_schema =3D \ ArraySchema("testing", desc=3D"Configuration for testing module", members=3D[ MapSchema("tests", "Tests", --=20 2.21.0 _______________________________________________ Patchew-devel mailing list Patchew-devel@redhat.com https://www.redhat.com/mailman/listinfo/patchew-devel From nobody Wed Apr 24 18:10:23 2024 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; 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 ARC-Seal: i=1; a=rsa-sha256; t=1555604519; cv=none; d=zoho.com; s=zohoarc; b=bGa/0I/kp6JwWX7Is8nMu6LWV2jAd96YMiEczqt1VFcLKG5HGfKljlCkIQJg3DwPKWyztj35fLrZDYrmkaxS/ElPZAQSeUfU+mrDgYO0ini8FwExMHzohgUrfvLLOjvz0qVqUW2TyAGM6vUKGcQUBK1z2s+4DzgBWb1vZx618R0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1555604519; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To:ARC-Authentication-Results; bh=esXftvnr9ks6nAieGNuPhQO+zIFNdFGhYID2DsZicM0=; b=ZxTNiAPI/wAmSqU3TG6+4mSU2QxOgriSYV7TFluPmrGTKIt15ukRfO548TKDDPwl6WxkPomhxKUmkcjiqYY+8iFYKGkWTjy5edtzUVbakflDX1Oj+Mku886Fk9tv0YEZqvJcYpWpNSlLC9P8tbaHS1p0Q2eZ/zhssVvZCpCIGHQ= ARC-Authentication-Results: i=1; mx.zoho.com; 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 header.from= (p=none dis=none) header.from= Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 1555604519954109.80173670775525; Thu, 18 Apr 2019 09:21:59 -0700 (PDT) Received: from smtp.corp.redhat.com (int-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.11]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id A94BC88ABB; Thu, 18 Apr 2019 16:21:58 +0000 (UTC) Received: from colo-mx.corp.redhat.com (colo-mx01.intmail.prod.int.phx2.redhat.com [10.5.11.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 9D7CD600C1; Thu, 18 Apr 2019 16:21:58 +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 94C05181AC45; Thu, 18 Apr 2019 16:21:58 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id x3IGLvp0009197 for ; Thu, 18 Apr 2019 12:21:57 -0400 Received: by smtp.corp.redhat.com (Postfix) id 55E6E60BF7; Thu, 18 Apr 2019 16:21:57 +0000 (UTC) Received: from donizetti.redhat.com (ovpn-112-46.ams2.redhat.com [10.36.112.46]) by smtp.corp.redhat.com (Postfix) with ESMTP id B197760BE5 for ; Thu, 18 Apr 2019 16:21:56 +0000 (UTC) From: Paolo Bonzini To: patchew-devel@redhat.com Date: Thu, 18 Apr 2019 18:21:42 +0200 Message-Id: <20190418162146.5695-7-pbonzini@redhat.com> In-Reply-To: <20190418162146.5695-1-pbonzini@redhat.com> References: <20190418162146.5695-1-pbonzini@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-loop: patchew-devel@redhat.com Subject: [Patchew-devel] [PATCH 06/10] mods: refactor extraction of configuration into a dictionary 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: , Content-Transfer-Encoding: quoted-printable Sender: patchew-devel-bounces@redhat.com Errors-To: patchew-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.11 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.26]); Thu, 18 Apr 2019 16:21:58 +0000 (UTC) Content-Type: text/plain; charset="utf-8" Both the email and testing modules have code to extract the configuration from project properties into a dictionary. Generalize that code, using the project_config_schema to drive the conversion, and use it in the git plugin as well This matches the way configuration will be stored in the database when we move away from project properties. In fact, all this nice visitor code will disappear very soon... --- mod.py | 37 ++++++++++++++++++++++++++++++++++++- mods/email.py | 14 +------------- mods/git.py | 28 ++++++++++++++++------------ mods/testing.py | 25 ++++--------------------- 4 files changed, 57 insertions(+), 47 deletions(-) diff --git a/mod.py b/mod.py index 319d4d3..d24e924 100644 --- a/mod.py +++ b/mod.py @@ -53,7 +53,7 @@ class PatchewModule(object): prefix =3D prefix + scm.name + "." def _build_map_items(): r =3D {} - for p, v in project.get_properties().items(): + for p in project.get_properties().keys(): if not p.startswith(prefix): continue name =3D p[len(prefix):] @@ -152,6 +152,41 @@ class PatchewModule(object): tmpl +=3D self._render_template(request, project, TMPL_END) return tmpl =20 + def _get_map_scm(self, project, prop_name, scm): + prefix =3D prop_name + "." + result =3D {} + for p in project.get_properties().keys(): + if not p.startswith(prefix): + continue + name =3D p[len(prefix):] + name =3D name[:name.rfind(".")] + if name in result: + continue + assert scm.item.name =3D=3D '{name}' + value =3D self._get_one(project, prefix + name, scm.item) + result[name] =3D value + return result + + def _get_array_scm(self, project, prop_name, scm): + prefix =3D prop_name + "." + result =3D {} + for i in scm.members: + assert i.name !=3D '{name}' + result[i.name] =3D self._get_one(project, prefix + i.name, i) + return result + + def _get_one(self, project, prop_name, scm): + if type(scm) =3D=3D MapSchema: + return self._get_map_scm(project, prop_name, scm) + elif type(scm) =3D=3D ArraySchema: + return self._get_array_scm(project, prop_name, scm) + else: + return project.get_property(prop_name) + + def get_project_config(self, project): + scm =3D self.project_config_schema + return self._get_one(project, scm.name, scm) + _loaded_modules =3D {} =20 def _module_init_config(cls): diff --git a/mods/email.py b/mods/email.py index 14553f0..58dac57 100644 --- a/mods/email.py +++ b/mods/email.py @@ -191,19 +191,7 @@ Email information is configured in "INI" style: return "<%s@patchew.org>" % uuid.uuid1() =20 def get_notifications(self, project): - ret =3D {} - for k, v in project.get_properties().items(): - if not k.startswith("email.notifications."): - continue - tn =3D k[len("email.notifications."):] - if "." not in tn: - continue - an =3D tn[tn.find(".") + 1:] - tn =3D tn[:tn.find(".")] - ret.setdefault(tn, {}) - ret[tn][an] =3D v - ret[tn]["name"] =3D tn - return ret + return self.get_project_config(project).get("notifications", {}) =20 def on_event(self, event, **params): class EmailCancelled(Exception): diff --git a/mods/git.py b/mods/git.py index 110f261..d459a0b 100644 --- a/mods/git.py +++ b/mods/git.py @@ -98,9 +98,6 @@ class GitModule(PatchewModule): if series.is_complete: self.mark_as_pending_apply(series) =20 - def get_project_config(self, project, what): - return project.get_property("git." + what) - def _is_repo(self, path): if not os.path.isdir(path): return False @@ -116,10 +113,14 @@ class GitModule(PatchewModule): =20 def get_mirror(self, po, request, format): response =3D {} - for key, prop in (("head", "git.head"), - ("pushurl", "git.push_to"), - ("url", "git.public_repo")): - response[key] =3D po.get_property(prop) or None + config =3D self.get_project_config(po) + if "push_to" in config: + response["pushurl"] =3D config["push_to"] + if "public_repo" in config: + response["url"] =3D config["public_repo"] + head =3D po.get_property("git.head") + if head: + response["head"] =3D head return response =20 def rest_project_fields_hook(self, request, fields): @@ -130,7 +131,9 @@ class GitModule(PatchewModule): =20 def get_projects_prepare_hook(self, project, response): response["git.head"] =3D project.get_property("git.head") - response["git.push_to"] =3D project.get_property("git.push_to") + config =3D self.get_project_config(project) + if "push_to" in config: + response["git.push_to"] =3D config["push_to"] =20 def prepare_message_hook(self, request, message, detailed): if not message.is_series_head: @@ -264,9 +267,9 @@ class ApplierGetView(APILoginRequiredView): "properties", "tags"= ]) =20 po =3D m.project - for prop in ["git.push_to", "git.public_repo", "git.url_template"]: - if po.get_property(prop): - response[prop] =3D po.get_property(prop) + config =3D _instance.get_project_config(po) + for k, v in config.items(): + response["git." + k] =3D v base =3D _instance.get_base(m) if base: response["git.repo"] =3D base.data["repo"] @@ -296,7 +299,8 @@ class ApplierReportView(APILoginRequiredView): if url: data['url'] =3D url elif tag: - url_template =3D p.get_property("git.url_template") + config =3D _instance.get_project_config(po) + url_template =3D config.get("url_template") if url_template: data['url'] =3D url_template.replace("%t", tag) if base: diff --git a/mods/testing.py b/mods/testing.py index 9df4ccd..9e41307 100644 --- a/mods/testing.py +++ b/mods/testing.py @@ -298,18 +298,7 @@ class TestingModule(PatchewModule): ret =3D {} if isinstance(obj, Message): obj =3D obj.project - for k, v in obj.get_properties().items(): - if not k.startswith("testing.tests."): - continue - tn =3D k[len("testing.tests."):] - if "." not in tn: - continue - an =3D tn[tn.find(".") + 1:] - tn =3D tn[:tn.find(".")] - ret.setdefault(tn, {}) - ret[tn][an] =3D v - ret[tn]["name"] =3D tn - return ret + return self.get_project_config(obj).get("tests", {}) =20 def _build_reset_ops(self, obj): if isinstance(obj, Message): @@ -409,15 +398,8 @@ class TestingModule(PatchewModule): project.extra_ops +=3D self._build_reset_ops(project) =20 def get_capability_probes(self, project): - ret =3D {} - for k, v in project.get_properties().items(): - prefix =3D "testing.requirements." - if not k.startswith(prefix): - continue - name =3D k[len(prefix):] - name =3D name[:name.find(".")] - ret[name] =3D v - return ret + props =3D self.get_project_config(project).get('requirements', {}) + return {k: v['script'] for k, v in props.items()} =20 def get_testing_probes(self, project, request, format): return self.get_capability_probes(project) @@ -494,6 +476,7 @@ class TestingGetView(APILoginRequiredView): if req not in capabilities: break else: + t["name"] =3D tn yield r, t =20 def _find_project_test(self, request, po, tester, capabilities): --=20 2.21.0 _______________________________________________ Patchew-devel mailing list Patchew-devel@redhat.com https://www.redhat.com/mailman/listinfo/patchew-devel From nobody Wed Apr 24 18:10:23 2024 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; 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 ARC-Seal: i=1; a=rsa-sha256; t=1555604522; cv=none; d=zoho.com; s=zohoarc; b=aaMYDHcgkLZaYTWbSbBrCiHKZDy3Ww9BkkmeNSRUA5Las8YKX8G5lTKrXWi6mswfz5NIULGCskHD/efPBxxJsWmPjqDCvHNdvNYmV1TTPPdyiZCUK5vFx7Mt3a1FTpNJ/597LN1DRKtUYpwMyhf5HLeWY2tWLMyIgqZpwDRS4zA= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1555604522; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To:ARC-Authentication-Results; bh=PNzdGe0G0iz87uODEyOirnFhMYflFjS0t4n8KQaz2/k=; b=Gc/SHksHOxYHAuFmMma/rCDSklz4Loi8+FP6Rtoai08eoL9N17wsvjDohyTa9OPalu8S0ipxo3uGvHYUG5zEvggwtjkBLPGLmszIy0Hka0v53eFokO78Xny4/vBr4ozKjg22cKXmboSHAJB08ADX0PyRXZ9MylxSYDgG3a4H1hc= ARC-Authentication-Results: i=1; mx.zoho.com; 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 header.from= (p=none dis=none) header.from= Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 1555604522976503.95538918825264; Thu, 18 Apr 2019 09:22:02 -0700 (PDT) Received: from smtp.corp.redhat.com (int-mx06.intmail.prod.int.phx2.redhat.com [10.5.11.16]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id C69DBC0546FA; Thu, 18 Apr 2019 16:22:01 +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 BC6635C21F; Thu, 18 Apr 2019 16:22:01 +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 B20BC4EA71; Thu, 18 Apr 2019 16:22:01 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id x3IGM0sn009212 for ; Thu, 18 Apr 2019 12:22:00 -0400 Received: by smtp.corp.redhat.com (Postfix) id 65F2060C61; Thu, 18 Apr 2019 16:22:00 +0000 (UTC) Received: from donizetti.redhat.com (ovpn-112-46.ams2.redhat.com [10.36.112.46]) by smtp.corp.redhat.com (Postfix) with ESMTP id 8705260BFC for ; Thu, 18 Apr 2019 16:21:57 +0000 (UTC) From: Paolo Bonzini To: patchew-devel@redhat.com Date: Thu, 18 Apr 2019 18:21:43 +0200 Message-Id: <20190418162146.5695-8-pbonzini@redhat.com> In-Reply-To: <20190418162146.5695-1-pbonzini@redhat.com> References: <20190418162146.5695-1-pbonzini@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-loop: patchew-devel@redhat.com Subject: [Patchew-devel] [PATCH 07/10] models: store plugin configuration in a single, separate JSONField 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: , Content-Transfer-Encoding: quoted-printable Sender: patchew-devel-bounces@redhat.com Errors-To: patchew-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.16 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.32]); Thu, 18 Apr 2019 16:22:01 +0000 (UTC) Content-Type: text/plain; charset="utf-8" This is the next step in the splitting the properties table into logically separate components. This time it's the turn of configuration data coming from the plugin schemas, which is extracted into a new JSON field. The configuration editor is simplified a bit because all the properties are sent to the server in a single shot and all configuration is overwritten. As a result, we no longer need the deletion API delete-project-properties-by-prefix. --- api/migrations/0049_project_config.py | 22 +++ .../0050_populate_project_config.py | 56 +++++++ api/models.py | 1 + api/views.py | 30 ++-- mod.py | 139 ++++-------------- mods/git.py | 27 ++-- mods/testing.py | 7 +- tests/patchewtest.py | 12 +- tests/test_git.py | 11 +- tests/test_testing.py | 35 +++-- 10 files changed, 185 insertions(+), 155 deletions(-) create mode 100644 api/migrations/0049_project_config.py create mode 100644 api/migrations/0050_populate_project_config.py diff --git a/api/migrations/0049_project_config.py b/api/migrations/0049_pr= oject_config.py new file mode 100644 index 0000000..69b5bd5 --- /dev/null +++ b/api/migrations/0049_project_config.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-04-18 12:56 +from __future__ import unicode_literals + +from django.db import migrations +import jsonfield.encoder +import jsonfield.fields + + +class Migration(migrations.Migration): + + dependencies =3D [ + ('api', '0048_populate_message_flags'), + ] + + operations =3D [ + migrations.AddField( + model_name=3D'project', + name=3D'config', + field=3Djsonfield.fields.JSONField(default=3D{}, dump_kwargs= =3D{'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_k= wargs=3D{}), + ), + ] diff --git a/api/migrations/0050_populate_project_config.py b/api/migration= s/0050_populate_project_config.py new file mode 100644 index 0000000..4db2e8a --- /dev/null +++ b/api/migrations/0050_populate_project_config.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations +from django.db.models import Q + +def properties_to_config(apps, schema_editor): + Project =3D apps.get_model('api', 'Project') + for po in Project.objects.all(): + q =3D Q(name__startswith=3D'testing.tests.') | \ + Q(name__startswith=3D'testing.requirements.') | \ + Q(name__startswith=3D'email.notifications.') | \ + Q(name__in=3D('git.push_to', 'git.url_template', 'git.public_r= epo')) + props =3D po.projectproperty_set.filter(q) + config =3D {} + for p in props: + *path, last =3D p.name.split('.') + parent =3D config + for item in path: + parent =3D parent.setdefault(item, {}) + parent[last] =3D p.value + #print(po, config) + po.config =3D config + po.save() + props.delete() + +def flatten_properties(source, prefix, result=3DNone): + if result is None: + result =3D {} + for k, v in source.items(): + if isinstance(v, dict): + flatten_properties(v, prefix + k + '.', result) + else: + result[prefix + k] =3D v + return result + +def config_to_properties(apps, schema_editor): + Project =3D apps.get_model('api', 'Project') + ProjectProperty =3D apps.get_model('api', 'ProjectProperty') + for po in Project.objects.all(): + props =3D flatten_properties(po.config, '') + for k, v in props.items(): + new_prop =3D ProjectProperty(project=3Dpo, name=3Dk, value=3Dv) + new_prop.save() + +class Migration(migrations.Migration): + + dependencies =3D [ + ('api', '0049_project_config'), + ] + + operations =3D [ + migrations.RunPython(properties_to_config, + reverse_code=3Dconfig_to_properties), + ] diff --git a/api/models.py b/api/models.py index 3176559..512bf50 100644 --- a/api/models.py +++ b/api/models.py @@ -171,6 +171,7 @@ class Project(models.Model): "top project which has " "parent_project=3DNULL")) maintainers =3D models.ManyToManyField(User, blank=3DTrue) + config =3D jsonfield.JSONField(default=3D{}) =20 def __str__(self): return self.name diff --git a/api/views.py b/api/views.py index 887f9ed..3e70ed7 100644 --- a/api/views.py +++ b/api/views.py @@ -13,6 +13,7 @@ from django.contrib.auth import authenticate, login, logo= ut from django.http import HttpResponse, Http404 from django.core.exceptions import PermissionDenied from django.conf import settings +from event import declare_event from .models import Project, Message import json from .search import SearchEngine @@ -129,28 +130,27 @@ class UpdateProjectHeadView(APILoginRequiredView): return ret =20 =20 -class SetProjectPropertiesView(APILoginRequiredView): - name =3D "set-project-properties" - allowed_groups =3D ["maintainers"] - - def handle(self, request, project, properties): - po =3D Project.objects.get(name=3Dproject) - if not po.maintained_by(request.user): - raise PermissionDenied("Access denied to this project") - for k, v in properties.items(): - po.set_property(k, v) +declare_event("SetProjectConfig", obj=3D"project whose configuration was u= pdated") =20 =20 -class DeleteProjectPropertiesByPrefixView(APILoginRequiredView): - name =3D "delete-project-properties-by-prefix" +class SetProjectConfigView(APILoginRequiredView): + name =3D "set-project-config" allowed_groups =3D ["maintainers"] =20 - def handle(self, request, project, prefix): + def handle(self, request, project, config): po =3D Project.objects.get(name=3Dproject) if not po.maintained_by(request.user): raise PermissionDenied("Access denied to this project") - for k in [x for x in po.get_properties().keys() if x.startswith(pr= efix)]: - po.set_property(k, None) + new_config =3D {} + for k, v in properties.items(): + *path, last =3D k.split('.') + parent =3D new_config + for item in path: + parent =3D parent.setdefault(item, {}) + parent[last] =3D v + po.config =3D new_config + po.save() + emit_event("SetProjectConfig", obj=3Dpo) =20 =20 def prepare_patch(p): diff --git a/mod.py b/mod.py index d24e924..1b750d3 100644 --- a/mod.py +++ b/mod.py @@ -49,29 +49,13 @@ class PatchewModule(object): data["module"] =3D self return Template(tmpl).render(Context(data)) =20 - def _build_map_scm(self, request, project, prefix, scm): - prefix =3D prefix + scm.name + "." - def _build_map_items(): - r =3D {} - for p in project.get_properties().keys(): - if not p.startswith(prefix): - continue - name =3D p[len(prefix):] - name =3D name[:name.rfind(".")] - if name in r: - continue - pref =3D prefix + name + "." - r[name] =3D { - "name": name, - "html": self._build_one(request, project, - pref, scm.item) - } - return list(r.values()) - - schema_html =3D self._build_one(request, project, prefix, - scm.item) + def _build_map_scm(self, request, project, prefix, config, scm): + schema_html =3D self._build_one(request, project, prefix, scm.item) item =3D {"html": schema_html} - items =3D _build_map_items() + items =3D [{ + "name": name, + "html": self._build_one(request, project, prefix + name + ".", + value, scm.item)} for name, value in c= onfig.items()] return self._render_template(request, project, TMPL_MAP, schema=3Dscm, item_schema=3Dscm.item, @@ -79,113 +63,69 @@ class PatchewModule(object): items=3Ditems, item=3Ditem) =20 - def _build_array_scm(self, request, project, prefix, scm): + def _build_array_scm(self, request, project, prefix, config, scm, show= _save_button): members =3D [self._build_one(request, project, - prefix, x) for x in scm.me= mbers] - show_save_button =3D False - for m in scm.members: - if type(m) =3D=3D StringSchema: - show_save_button =3D True - break + prefix + x.name + ".", + config[x.name], x) for x in scm.members] return self._render_template(request, project, TMPL_ARRAY, schema=3Dscm, members=3Dmembers, show_save_button=3Dshow_save_button, prefix=3Dprefix) =20 - def _build_string_scm(self, request, project, prefix, scm): - prop_name =3D prefix + scm.name - prop_value =3D project.get_property(prop_name) + def _build_string_scm(self, request, project, prefix, config, scm): return self._render_template(request, project, TMPL_STRING, schema=3Dscm, name=3Dscm.name, prefix=3Dprefix, - value=3Dprop_value) + value=3Dconfig) =20 - def _build_integer_scm(self, request, project, prefix, scm): - prop_name =3D prefix + scm.name - prop_value =3D project.get_property(prop_name) + def _build_integer_scm(self, request, project, config, scm): return self._render_template(request, project, TMPL_INTEGER, schema=3Dscm, name=3Dscm.name, prefix=3Dprefix, - value=3Dprop_value) + value=3Dconfig) =20 - def _build_boolean_scm(self, request, project, prefix, scm): - prop_name =3D prefix + scm.name - prop_value =3D project.get_property(prop_name) + def _build_boolean_scm(self, request, project, config, scm): return self._render_template(request, project, TMPL_BOOLEAN, schema=3Dscm, name=3Dscm.name, prefix=3Dprefix, - value=3Dprop_value) + value=3Dconfig) =20 - def _build_enum_scm(self, request, project, prefix, scm): - prop_name =3D prefix + scm.name - prop_value =3D project.get_property(prop_name) + def _build_enum_scm(self, request, project, config, scm): return self._render_template(request, project, TMPL_ENUM, schema=3Dscm, name=3Dscm.name, prefix=3Dprefix, - value=3Dprop_value) + value=3Dconfig) =20 - def _build_one(self, request, project, prefix, scm): + def _build_one(self, request, project, prefix, config, scm, show_save_= button=3DFalse): if type(scm) =3D=3D MapSchema: - return self._build_map_scm(request, project, prefix, scm) + return self._build_map_scm(request, project, prefix, config, s= cm) elif type(scm) =3D=3D StringSchema: - return self._build_string_scm(request, project, prefix, scm) + return self._build_string_scm(request, project, prefix, config= , scm) elif type(scm) =3D=3D IntegerSchema: - return self._build_integer_scm(request, project, prefix, scm) + return self._build_integer_scm(request, project, prefix, confi= g, scm) elif type(scm) =3D=3D BooleanSchema: - return self._build_boolean_scm(request, project, prefix, scm) + return self._build_boolean_scm(request, project, prefix, confi= g, scm) elif type(scm) =3D=3D EnumSchema: - return self._build_enum_scm(request, project, prefix, scm) + return self._build_enum_scm(request, project, prefix, config, = scm) elif type(scm) =3D=3D ArraySchema: - return self._build_array_scm(request, project, prefix, scm) + return self._build_array_scm(request, project, prefix, config,= scm, show_save_button) assert False =20 def build_config_html(self, request, project): - assert not isinstance(self.project_config_schema, StringSchema) - assert not isinstance(self.project_config_schema, IntegerSchema) + assert isinstance(self.project_config_schema, ArraySchema) scm =3D self.project_config_schema - tmpl =3D self._build_one(request, project, scm.name + ".", scm) + config =3D self.get_project_config(project) + tmpl =3D self._build_one(request, project, scm.name + ".", config,= scm, True) tmpl +=3D self._render_template(request, project, TMPL_END) return tmpl =20 - def _get_map_scm(self, project, prop_name, scm): - prefix =3D prop_name + "." - result =3D {} - for p in project.get_properties().keys(): - if not p.startswith(prefix): - continue - name =3D p[len(prefix):] - name =3D name[:name.rfind(".")] - if name in result: - continue - assert scm.item.name =3D=3D '{name}' - value =3D self._get_one(project, prefix + name, scm.item) - result[name] =3D value - return result - - def _get_array_scm(self, project, prop_name, scm): - prefix =3D prop_name + "." - result =3D {} - for i in scm.members: - assert i.name !=3D '{name}' - result[i.name] =3D self._get_one(project, prefix + i.name, i) - return result - - def _get_one(self, project, prop_name, scm): - if type(scm) =3D=3D MapSchema: - return self._get_map_scm(project, prop_name, scm) - elif type(scm) =3D=3D ArraySchema: - return self._get_array_scm(project, prop_name, scm) - else: - return project.get_property(prop_name) - def get_project_config(self, project): - scm =3D self.project_config_schema - return self._get_one(project, scm.name, scm) + return project.config.get(self.project_config_schema.name, {}) =20 _loaded_modules =3D {} =20 @@ -423,9 +363,9 @@ function properties_save(btn) { $(btn).addClass("disabled"); $(btn).text("Saving..."); $(btn).parent().find(".save-message").remove(); - patchew_api_do("set-project-properties", + patchew_api_do("set-project-config", { project: "{{ project.name }}", - properties: props }) + config: props }) .done(function (data) { save_done(btn, true); }) @@ -466,23 +406,8 @@ function map_delete_item(btn) { if (!window.confirm("Really delete '" + name +"'?")) { return; } - $(btn).addClass("disabled"); - $(btn).text("Deleting..."); - $(btn).parent().find(".delete-message").remove(); - patchew_api_do("delete-project-properties-by-prefix", - { project: "{{ project.name }}", - prefix: prefix }) - .done(function (data) { - container =3D $(btn).parent().parent().parent(); - container.remove(); - }) - .fail(function (data, text, error) { - $(btn).removeClass("disabled"); - $(btn).text("Delete"); - info =3D $("
"); - info.html("Error: " + error); - info.insertBefore($(btn)); - }); + container =3D $(btn).parent().parent().parent(); + container.remove(); } function enum_change(which) { val =3D $(which).val(); diff --git a/mods/git.py b/mods/git.py index d459a0b..8e868c2 100644 --- a/mods/git.py +++ b/mods/git.py @@ -246,19 +246,20 @@ class ApplierGetView(APILoginRequiredView): def handle(self, request, target_repo=3DNone): q =3D Message.objects.filter(results__name=3D"git", results__statu= s=3D"pending") if target_repo is not None and target_repo !=3D '': - props =3D ProjectProperty.objects.filter(name=3D'git.push_to') - # unfortunately startswith does not work with JSONFields, so w= e have to hack - # around it and look at the raw database representation. This= would fail - # if we used PostgreSQL json fields! - target_repo_escaped =3D json.dumps(target_repo) - if target_repo[-1] !=3D '/': - props =3D props.extra(where=3D["value =3D %s or value like= %s"], - params=3D[target_repo_escaped, - target_repo_escaped[0:-1] + '/= %']) - else: - props =3D props.extra(where=3D["value like %s"], - params=3Dtarget_repo_escaped[0:-1] + '= %') - q =3D q.filter(project__in=3D[prop.project for prop in props.a= ll()]) + # Postgres could use JSON fields instead. Fortunately project= s are + # few so this is cheap + def match_target_repo(config, target_repo): + push_to =3D config.get('git', {}).get('push_to') + if push_to is None: + return False + if target_repo[-1] !=3D '/': + return push_to =3D=3D target_repo or push_to.startswit= h(target_repo + '/') + else: + return push_to.startswith(target_repo) + + projects =3D Project.objects.values_list('id', 'config').all() + projects =3D [pid for pid, config in projects if match_target_= repo(config, target_repo)] + q =3D q.filter(project__pk__in=3Dprojects) m =3D q.first() if not m: return None diff --git a/mods/testing.py b/mods/testing.py index 9e41307..969ce84 100644 --- a/mods/testing.py +++ b/mods/testing.py @@ -117,15 +117,16 @@ class TestingModule(PatchewModule): html_log_url=3D"URL to test log (HTML)", is_timeout=3D"whether the test has timeout") register_handler("SetProperty", self.on_set_property) + register_handler("SetProjectConfig", self.on_set_config) register_handler("ResultUpdate", self.on_result_update) =20 def on_set_property(self, evt, obj, name, value, old_value): if isinstance(obj, Project) and name =3D=3D "git.head" \ and old_value !=3D value: self.clear_and_start_testing(obj) - elif isinstance(obj, Project) and name.startswith("testing.tests."= ) \ - and old_value !=3D value: - self.project_recalc_pending_tests(obj) + + def on_set_config(self, evt, obj): + self.project_recalc_pending_tests(obj) =20 def get_msg_base_tags(self, msg): return [t for t in msg.tags if t.lower().startswith("based-on:")] diff --git a/tests/patchewtest.py b/tests/patchewtest.py index ade039d..e58e5b9 100644 --- a/tests/patchewtest.py +++ b/tests/patchewtest.py @@ -127,11 +127,15 @@ class PatchewTestCase(dj_test.LiveServerTestCase): =20 def add_project(self, name, mailing_list=3D"", git_repo=3D""): p =3D Project(name=3Dname, mailing_list=3Dmailing_list, git=3Dgit_= repo or self.create_git_repo(name)) - p.save() push_repo =3D self.create_git_repo(name + "_push") - p.set_property("git.push_to", push_repo) - p.set_property("git.public_repo", push_repo) - p.set_property("git.url_template", push_repo) + p.config =3D { + "git": { + "push_to": push_repo, + "public_repo": push_repo, + "url_template": push_repo + } + } + p.save() return p =20 def api_login(self): diff --git a/tests/test_git.py b/tests/test_git.py index 09545df..0b9fa50 100755 --- a/tests/test_git.py +++ b/tests/test_git.py @@ -22,9 +22,14 @@ class GitTest(PatchewTestCase): self.cli_login() self.repo =3D self.create_git_repo() self.p =3D self.add_project("QEMU", "qemu-devel@nongnu.org", git_r= epo=3Dself.repo) - self.p.set_property("git.push_to", self.repo) - self.p.set_property("git.public_repo", self.repo) - self.p.set_property("git.url_template", self.repo + " %t") + self.p.config =3D { + "git": { + "push_to": self.repo, + "public_repo": self.repo, + "url_template": self.repo + " %t" + } + } + self.p.save() self.PROJECT_BASE =3D '%sprojects/%d/' % (self.REST_BASE, self.p.i= d) =20 def cleanUp(self): diff --git a/tests/test_testing.py b/tests/test_testing.py index 630e7f9..2d21654 100755 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -13,16 +13,31 @@ import subprocess =20 from api.models import Message, Result from api.search import FLAG_TESTED +from event import emit_event =20 from .patchewtest import PatchewTestCase, main =20 =20 def create_test(project, name, requirements=3D"", script=3D"#!/bin/bash\nt= rue"): - prefix =3D "testing.tests." + name + "." - project.set_property(prefix + "timeout", 3600) - project.set_property(prefix + "enabled", True) - project.set_property(prefix + "script", script) - project.set_property(prefix + "requirements", requirements) + testing =3D project.config.setdefault('testing', {}) + tests =3D testing.setdefault('tests', {}) + tests[name] =3D { + 'timeout': 3600, + 'enabled': True, + 'script': script, + 'requirements': requirements + } + project.save() + emit_event("SetProjectConfig", obj=3Dproject) + + +def create_requirement(project, name, script=3D"#!/bin/bash\ntrue"): + testing =3D project.config.setdefault('testing', {}) + requirements =3D testing.setdefault('requirements', {}) + requirements[name] =3D { + 'script': script, + } + project.save() =20 =20 class TestingTestCase(PatchewTestCase, metaclass=3Dabc.ABCMeta): @@ -220,13 +235,11 @@ class TesterTest(PatchewTestCase): create_test(self.p2, "b") =20 self.p3 =3D self.add_project("ALLOW", "qemu-devel@nongnu.org") - self.p3.set_property("testing.requirements.allow.script", - "#!/bin/sh\ntrue") + create_requirement(self.p3, 'allow', "#!/bin/sh\ntrue") create_test(self.p3, "c", "allow") =20 self.p4 =3D self.add_project("DENY", "qemu-devel@nongnu.org") - self.p4.set_property("testing.requirements.deny.script", - "#!/bin/sh\nfalse") + create_requirement(self.p4, 'deny', "#!/bin/sh\nfalse") create_test(self.p4, "d", "deny") =20 self.cli_login() @@ -389,7 +402,9 @@ class TestingDisableTest(PatchewTestCase): self.cli_login() self.cli_import('0013-foo-patch.mbox.gz') self.do_apply() - self.p1.set_property("testing.tests.a.enabled", False) + self.p1.config['testing']['tests']['a']['enabled'] =3D False + self.p1.save() + emit_event("SetProjectConfig", obj=3Dself.p1) out, err =3D self.check_cli(["tester", "-p", "QEMU", "--no-wait"]) self.assertNotIn("Project: QEMU\n", out) self.cli_logout() --=20 2.21.0 _______________________________________________ Patchew-devel mailing list Patchew-devel@redhat.com https://www.redhat.com/mailman/listinfo/patchew-devel From nobody Wed Apr 24 18:10:23 2024 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; 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 ARC-Seal: i=1; a=rsa-sha256; t=1555604525; cv=none; d=zoho.com; s=zohoarc; b=FcFXFYeYfKsYSdY6i9FPkukfZ32z5dkmFtTcN5G2BRcCWeJStSUrVLCOkFda7vxHvfq4wBfNn+ceU0W1gHTPe+7DCfkZSyeGdpCjaDplTAFA6Xx0rVpJ1fjkbCHRa1dXuZ52RzVz+TDQeiedSr0DZMUmp37/cifPsrK+KsrJkus= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1555604525; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To:ARC-Authentication-Results; bh=vqy9sJCsQIih9yWfzJPk7D72KUnnxQkUC1pPv8R1HaU=; b=Npng/X/MTShJL1ICd+HdY3jdYMYqRqiY2eReRhYkFBE7nVKTiSjGW/supbicLd1fVIyap8pGiLozt/FAbTySS0azAoXBw/SnPbqfw0lj0EA0AQdl0CLvzaeYBaS8VKjxaWzNxchv9cLoWOqbWjT2LjY1l0wuiQE2BiZS3dh7/Lw= ARC-Authentication-Results: i=1; mx.zoho.com; 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 header.from= (p=none dis=none) header.from= Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 1555604525975564.6141376273549; Thu, 18 Apr 2019 09:22:05 -0700 (PDT) Received: from smtp.corp.redhat.com (int-mx05.intmail.prod.int.phx2.redhat.com [10.5.11.15]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id C33E413A82; Thu, 18 Apr 2019 16:22:03 +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 B8E265D705; Thu, 18 Apr 2019 16:22:03 +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 AD0054EA71; Thu, 18 Apr 2019 16:22:03 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id x3IGM2hj009222 for ; Thu, 18 Apr 2019 12:22:02 -0400 Received: by smtp.corp.redhat.com (Postfix) id 3EE7460BF7; Thu, 18 Apr 2019 16:22:02 +0000 (UTC) Received: from donizetti.redhat.com (ovpn-112-46.ams2.redhat.com [10.36.112.46]) by smtp.corp.redhat.com (Postfix) with ESMTP id 7B54360BFC for ; Thu, 18 Apr 2019 16:22:00 +0000 (UTC) From: Paolo Bonzini To: patchew-devel@redhat.com Date: Thu, 18 Apr 2019 18:21:44 +0200 Message-Id: <20190418162146.5695-9-pbonzini@redhat.com> In-Reply-To: <20190418162146.5695-1-pbonzini@redhat.com> References: <20190418162146.5695-1-pbonzini@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-loop: patchew-devel@redhat.com Subject: [Patchew-devel] [PATCH 08/10] models: add property fields to Message 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: , Content-Transfer-Encoding: quoted-printable Sender: patchew-devel-bounces@redhat.com Errors-To: patchew-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.15 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.29]); Thu, 18 Apr 2019 16:22:03 +0000 (UTC) Content-Type: text/plain; charset="utf-8" This is the first step towards storing properties in a single JSON dictionary, which is simpler now that properties are fewer and smaller than they used to be. The related name "properties" has to be freed up for the new field. --- api/migrations/0051_auto_20190418_1346.py | 33 +++++++++++++++++++++++ api/models.py | 9 ++++--- 2 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 api/migrations/0051_auto_20190418_1346.py diff --git a/api/migrations/0051_auto_20190418_1346.py b/api/migrations/005= 1_auto_20190418_1346.py new file mode 100644 index 0000000..7d57a34 --- /dev/null +++ b/api/migrations/0051_auto_20190418_1346.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-04-18 13:46 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import jsonfield.encoder +import jsonfield.fields + + +class Migration(migrations.Migration): + + dependencies =3D [ + ('api', '0050_populate_project_config'), + ] + + operations =3D [ + migrations.AddField( + model_name=3D'message', + name=3D'properties', + field=3Djsonfield.fields.JSONField(default=3D{}, dump_kwargs= =3D{'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_k= wargs=3D{}), + ), + migrations.AddField( + model_name=3D'project', + name=3D'properties', + field=3Djsonfield.fields.JSONField(default=3D{}, dump_kwargs= =3D{'cls': jsonfield.encoder.JSONEncoder, 'separators': (',', ':')}, load_k= wargs=3D{}), + ), + migrations.AlterField( + model_name=3D'messageproperty', + name=3D'message', + field=3Dmodels.ForeignKey(on_delete=3Ddjango.db.models.deletio= n.CASCADE, to=3D'api.Message'), + ), + ] diff --git a/api/models.py b/api/models.py index 512bf50..7c3aefb 100644 --- a/api/models.py +++ b/api/models.py @@ -172,6 +172,7 @@ class Project(models.Model): "parent_project=3DNULL")) maintainers =3D models.ManyToManyField(User, blank=3DTrue) config =3D jsonfield.JSONField(default=3D{}) + properties =3D jsonfield.JSONField(default=3D{}) =20 def __str__(self): return self.name @@ -348,7 +349,7 @@ class MessageManager(models.Manager): return None else: q =3D super(MessageManager, self).get_queryset() - return q.filter(is_series_head=3DTrue).prefetch_related('propertie= s', 'project') + return q.filter(is_series_head=3DTrue).prefetch_related('messagepr= operty_set', 'project') =20 def find_series(self, message_id, project_name=3DNone): heads =3D self.series_heads(project_name) @@ -510,6 +511,7 @@ class Message(models.Model): =20 maintainers =3D jsonfield.JSONField(blank=3DTrue, default=3D[]) flags =3D models.CharField(max_length=3D64, blank=3DTrue, default=3D"") + properties =3D jsonfield.JSONField(default=3D{}) =20 def save_mbox(self, mbox_blob): save_blob(mbox_blob, self.message_id) @@ -621,7 +623,7 @@ class Message(models.Model): # The prefetch cache is invalidated, query again all_props =3D MessageProperty.objects.filter(message=3Dsel= f) else: - all_props =3D self.properties.all() + all_props =3D self.messageproperty_set.all() r =3D {} for m in all_props: r[m.name] =3D m.value @@ -783,8 +785,7 @@ class MessageResult(Result): =20 =20 class MessageProperty(models.Model): - message =3D models.ForeignKey('Message', on_delete=3Dmodels.CASCADE, - related_name=3D'properties') + message =3D models.ForeignKey('Message', on_delete=3Dmodels.CASCADE) name =3D models.CharField(max_length=3D256) value =3D jsonfield.JSONField() =20 --=20 2.21.0 _______________________________________________ Patchew-devel mailing list Patchew-devel@redhat.com https://www.redhat.com/mailman/listinfo/patchew-devel From nobody Wed Apr 24 18:10:23 2024 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; 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 ARC-Seal: i=1; a=rsa-sha256; t=1555604530; cv=none; d=zoho.com; s=zohoarc; b=DagOegnB0Jssdx2sRm+P7cFGbLKG1PbuGyBxheVVxgUOqdRd13VZzc4N8Oj7KFrXskd/WSykgeiNkDk3n7aQlCKxD/FjzYMwMGuwS4ArBlM9qPvJtyaFGfDPfO81GcYdN7T6KySmeMFQKoqB3yi+RymffIuJvqHI7a1AeVNp2bc= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1555604530; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To:ARC-Authentication-Results; bh=RtHNiCKp/WdJgB1sbhcGS/ExtRMB0bpAvojhQYXLHqI=; b=ad20zLIoIZW0Yi0hnJpjhbWpAllhD8s+B0t8ce35ZsWXTV1K9qPc99dje7Z4/jfVJg06oHKn1O/OszYx/znVjjoJLxJc/VzVEB+K0jtaf0/M0JDakL2YaMo+nhevEUMP3ZaCyrXUmdmTooNMUnL6OdsV4zdt1GIVGIDHNfSOe6w= ARC-Authentication-Results: i=1; mx.zoho.com; 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 header.from= (p=none dis=none) header.from= Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 1555604530304349.3880554081602; Thu, 18 Apr 2019 09:22:10 -0700 (PDT) Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 24F88308A111; Thu, 18 Apr 2019 16:22: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 191AE5D9CC; Thu, 18 Apr 2019 16:22: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 0C7044EA71; Thu, 18 Apr 2019 16:22:09 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id x3IGM8on009234 for ; Thu, 18 Apr 2019 12:22:08 -0400 Received: by smtp.corp.redhat.com (Postfix) id 421DC60BF7; Thu, 18 Apr 2019 16:22:08 +0000 (UTC) Received: from donizetti.redhat.com (ovpn-112-46.ams2.redhat.com [10.36.112.46]) by smtp.corp.redhat.com (Postfix) with ESMTP id 8DD5B60C61 for ; Thu, 18 Apr 2019 16:22:02 +0000 (UTC) From: Paolo Bonzini To: patchew-devel@redhat.com Date: Thu, 18 Apr 2019 18:21:45 +0200 Message-Id: <20190418162146.5695-10-pbonzini@redhat.com> In-Reply-To: <20190418162146.5695-1-pbonzini@redhat.com> References: <20190418162146.5695-1-pbonzini@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-loop: patchew-devel@redhat.com Subject: [Patchew-devel] [PATCH 09/10] models: switch from property tables to field 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: , Content-Transfer-Encoding: quoted-printable Sender: patchew-devel-bounces@redhat.com Errors-To: patchew-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.44]); Thu, 18 Apr 2019 16:22:09 +0000 (UTC) Content-Type: text/plain; charset="utf-8" Fortunately, most accesses to the property tables were hidden behind accessors. Therefore, apart from the usual ugly migration (which in this case is more or less a copy of the config migration) it is almost enough to change the accessors to look into the JSON dictionary. We can also use the new API to access a whole subset of the properties, which simplifies the access to testing.check_in.*. --- .../0052_populate_property_fields.py | 67 +++++++++++ api/models.py | 109 ++++++++++-------- mods/testing.py | 6 +- 3 files changed, 128 insertions(+), 54 deletions(-) create mode 100644 api/migrations/0052_populate_property_fields.py diff --git a/api/migrations/0052_populate_property_fields.py b/api/migratio= ns/0052_populate_property_fields.py new file mode 100644 index 0000000..432e809 --- /dev/null +++ b/api/migrations/0052_populate_property_fields.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations +from django.db.models import Q + +def do_properties_to_field(obj, propset): + properties =3D {} + props =3D propset.all() + for p in props: + *path, last =3D p.name.split('.') + parent =3D properties + for item in path: + parent =3D parent.setdefault(item, {}) + parent[last] =3D p.value + obj.properties =3D properties + #print(obj, properties) + obj.save() + props.delete() + +def properties_to_field(apps, schema_editor): + Project =3D apps.get_model('api', 'Project') + for po in Project.objects.all(): + do_properties_to_field(po, po.projectproperty_set) + Message =3D apps.get_model('api', 'Message') + for po in Message.objects.all(): + do_properties_to_field(po, po.messageproperty_set) + +def flatten_properties(source, prefix, result=3DNone): + if result is None: + result =3D {} + for k, v in source.items(): + if isinstance(v, dict): + flatten_properties(v, prefix + k + '.', result) + else: + result[prefix + k] =3D v + return result + +def do_field_to_properties(source, propclass, **kwargs): + props =3D flatten_properties(source, '') + for k, v in props.items(): + #print(k, v) + new_prop =3D propclass(name=3Dk, value=3Dv, **kwargs) + new_prop.save() + +def field_to_properties(apps, schema_editor): + Project =3D apps.get_model('api', 'Project') + ProjectProperty =3D apps.get_model('api', 'ProjectProperty') + for po in Project.objects.all(): + do_field_to_properties(po.properties, ProjectProperty, project=3Dp= o) + Message =3D apps.get_model('api', 'Message') + MessageProperty =3D apps.get_model('api', 'MessageProperty') + for m in Message.objects.all(): + do_field_to_properties(m.properties, MessageProperty, message=3Dm) + + +class Migration(migrations.Migration): + + dependencies =3D [ + ('api', '0051_auto_20190418_1346'), + ] + + operations =3D [ + migrations.RunPython(properties_to_field, + reverse_code=3Dfield_to_properties), + ] diff --git a/api/models.py b/api/models.py index 7c3aefb..2a6c09b 100644 --- a/api/models.py +++ b/api/models.py @@ -182,30 +182,39 @@ class Project(models.Model): return self.objects.filter(name=3Dproject).exists() =20 def get_property(self, prop, default=3DNone): - a =3D ProjectProperty.objects.filter(project=3Dself, name=3Dprop).= first() - if a: - return a.value - else: - return default - - def get_properties(self): - r =3D {} - for m in ProjectProperty.objects.filter(project=3Dself): - r[m.name] =3D m.value - return r - - def _do_set_property(self, prop, value): - if value is None: - ProjectProperty.objects.filter(project=3Dself, name=3Dprop).de= lete() + x =3D self.properties + *path, last =3D prop.split('.') + for item in path: + if not item in x: + return default + x =3D x[item] + return x.get(last, default) + + def delete_property(self, prop): + x =3D self.properties + *path, last =3D prop.split('.') + for item in path: + if not item in x: + return + x =3D x[item] + if not last in x: return - pp, created =3D ProjectProperty.objects.get_or_create(project=3Dse= lf, - name=3Dprop) - pp.value =3D value - pp.save() + old_val =3D x[last] + del x[last] + self.save() + emit_event("SetProperty", obj=3Dself, name=3Dprop, value=3DNone, + old_value=3Dold_val) =20 def set_property(self, prop, value): - old_val =3D self.get_property(prop) - self._do_set_property(prop, value) + if value is None: + self.delete_property(prop) + x =3D self.properties + *path, last =3D prop.split('.') + for item in path: + x =3D x.setdefault(item, {}) + old_val =3D x.get(last) + x[last] =3D value + self.save() emit_event("SetProperty", obj=3Dself, name=3Dprop, value=3Dvalue, old_value=3Dold_val) =20 @@ -613,37 +622,39 @@ class Message(models.Model): self.save() =20 def get_property(self, prop, default=3DNone): - return self.get_properties().get(prop, default) - - def get_properties(self): - if hasattr(self, '_properties'): - if self._properties is not None: - return self._properties - else: - # The prefetch cache is invalidated, query again - all_props =3D MessageProperty.objects.filter(message=3Dsel= f) - else: - all_props =3D self.messageproperty_set.all() - r =3D {} - for m in all_props: - r[m.name] =3D m.value - self._properties =3D r - return r - - def _do_set_property(self, prop, value): - if value is None: - MessageProperty.objects.filter(message=3Dself, name=3Dprop).de= lete() + x =3D self.properties + *path, last =3D prop.split('.') + for item in path: + if not item in x: + return default + x =3D x[item] + return x.get(last, default) + + def delete_property(self, prop): + x =3D self.properties + *path, last =3D prop.split('.') + for item in path: + if not item in x: + return + x =3D x[item] + if not last in x: return - mp, created =3D MessageProperty.objects.get_or_create(message=3Dse= lf, - name=3Dprop) - mp.value =3D value - mp.save() - # Invalidate cache - self._properties =3D None + old_val =3D x[last] + del x[last] + self.save() + emit_event("SetProperty", obj=3Dself, name=3Dprop, value=3DNone, + old_value=3Dold_val) =20 def set_property(self, prop, value): - old_val =3D self.get_property(prop) - self._do_set_property(prop, value) + if value is None: + self.delete_property(prop) + x =3D self.properties + *path, last =3D prop.split('.') + for item in path: + x =3D x.setdefault(item, {}) + old_val =3D x.get(last) + x[last] =3D value + self.save() emit_event("SetProperty", obj=3Dself, name=3Dprop, value=3Dvalue, old_value=3Dold_val) =20 diff --git a/mods/testing.py b/mods/testing.py index 969ce84..e5562ba 100644 --- a/mods/testing.py +++ b/mods/testing.py @@ -369,14 +369,10 @@ class TestingModule(PatchewModule): =20 def check_active_testers(self, project): at =3D [] - for k, v in project.get_properties().items(): - prefix =3D "testing.check_in." - if not k.startswith(prefix): - continue + for tn, v in project.get_property('testing.check_in', {}).items(): age =3D time.time() - v if age > 10 * 60: continue - tn =3D k[len(prefix):] at.append("%s (%dmin)" % (tn, math.ceil(age / 60))) if not at: return --=20 2.21.0 _______________________________________________ Patchew-devel mailing list Patchew-devel@redhat.com https://www.redhat.com/mailman/listinfo/patchew-devel From nobody Wed Apr 24 18:10:23 2024 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; 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 ARC-Seal: i=1; a=rsa-sha256; t=1555604533; cv=none; d=zoho.com; s=zohoarc; b=UmAOGuuUk26/UkvnQjXP60wyDtL9BfBHzvVBQbJ8f3zJOEW3Fzg74mTZAhHulTqHXJLlDZe90MGz8Y4MGXBvbpwig8uoddqpKX9H0+4cZIvVjmKiquQaCzRv42bo9fUa8+bQPnVkVl+b3wfkIenqOlGoWxGsAb3ycTT+wFNS7Co= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zoho.com; s=zohoarc; t=1555604533; h=Content-Type:Content-Transfer-Encoding:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:To:ARC-Authentication-Results; bh=/enxxNb+xscvwa9v33YrUSUMk+Mo43YJ5xF+s/XdHWk=; b=M8chEJ9hXj7RNgeYCCvVmyANQRILtuVi19HqbCOqcD9ZHpt6zB53yxZfpdtSnjtpexug0M/M1OrIullzOJma4o7pzPlvfsY3ud6x8lQerR9yqaajFyYRtQLWWSWQYTThbZGPMK0jJaLNoXsojxJrEBEv3MbJAYcYmo4uk2bktcI= ARC-Authentication-Results: i=1; mx.zoho.com; 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 header.from= (p=none dis=none) header.from= Return-Path: Received: from mx1.redhat.com (mx1.redhat.com [209.132.183.28]) by mx.zohomail.com with SMTPS id 1555604533427164.46647943553126; Thu, 18 Apr 2019 09:22:13 -0700 (PDT) Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.phx2.redhat.com [10.5.11.13]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 3CD69C0270D3; Thu, 18 Apr 2019 16:22:12 +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 2F8706085B; Thu, 18 Apr 2019 16:22:12 +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 CD0ED4EA71; Thu, 18 Apr 2019 16:22:11 +0000 (UTC) Received: from smtp.corp.redhat.com (int-mx02.intmail.prod.int.phx2.redhat.com [10.5.11.12]) by lists01.pubmisc.prod.ext.phx2.redhat.com (8.13.8/8.13.8) with ESMTP id x3IGMA7A009244 for ; Thu, 18 Apr 2019 12:22:10 -0400 Received: by smtp.corp.redhat.com (Postfix) id 42B6C60BFC; Thu, 18 Apr 2019 16:22:10 +0000 (UTC) Received: from donizetti.redhat.com (ovpn-112-46.ams2.redhat.com [10.36.112.46]) by smtp.corp.redhat.com (Postfix) with ESMTP id 834BF60BF7 for ; Thu, 18 Apr 2019 16:22:08 +0000 (UTC) From: Paolo Bonzini To: patchew-devel@redhat.com Date: Thu, 18 Apr 2019 18:21:46 +0200 Message-Id: <20190418162146.5695-11-pbonzini@redhat.com> In-Reply-To: <20190418162146.5695-1-pbonzini@redhat.com> References: <20190418162146.5695-1-pbonzini@redhat.com> MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.12 X-loop: patchew-devel@redhat.com Subject: [Patchew-devel] [PATCH 10/10] models: remove property tables 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: , Content-Transfer-Encoding: quoted-printable Sender: patchew-devel-bounces@redhat.com Errors-To: patchew-devel-bounces@redhat.com X-Scanned-By: MIMEDefang 2.79 on 10.5.11.13 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.32]); Thu, 18 Apr 2019 16:22:12 +0000 (UTC) Content-Type: text/plain; charset="utf-8" --- api/admin.py | 14 +-------- api/migrations/0053_auto_20190418_1357.py | 37 +++++++++++++++++++++++ api/models.py | 29 +----------------- api/search.py | 2 +- mods/git.py | 3 +- mods/testing.py | 3 +- 6 files changed, 42 insertions(+), 46 deletions(-) create mode 100644 api/migrations/0053_auto_20190418_1357.py diff --git a/api/admin.py b/api/admin.py index bd9e85b..ef0e21b 100644 --- a/api/admin.py +++ b/api/admin.py @@ -9,27 +9,15 @@ # http://opensource.org/licenses/MIT. =20 from django.contrib import admin -from .models import Message, MessageProperty, Module, Project, ProjectProp= erty +from .models import Message, Module, Project from mod import get_module =20 =20 -class ProjectPropertyInline(admin.TabularInline): - model =3D ProjectProperty - extra =3D 0 - - class ProjectAdmin(admin.ModelAdmin): filter_horizontal =3D ('maintainers',) - inlines =3D [ProjectPropertyInline] - - -class MessagePropertyInline(admin.TabularInline): - model =3D MessageProperty - extra =3D 0 =20 =20 class MessageAdmin(admin.ModelAdmin): - inlines =3D [MessagePropertyInline] list_filter =3D [('is_series_head')] search_fields =3D [ 'message_id', diff --git a/api/migrations/0053_auto_20190418_1357.py b/api/migrations/005= 3_auto_20190418_1357.py new file mode 100644 index 0000000..4621bcd --- /dev/null +++ b/api/migrations/0053_auto_20190418_1357.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2019-04-18 13:57 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies =3D [ + ('api', '0052_populate_property_fields'), + ] + + operations =3D [ + migrations.AlterUniqueTogether( + name=3D'messageproperty', + unique_together=3Dset([]), + ), + migrations.RemoveField( + model_name=3D'messageproperty', + name=3D'message', + ), + migrations.AlterUniqueTogether( + name=3D'projectproperty', + unique_together=3Dset([]), + ), + migrations.RemoveField( + model_name=3D'projectproperty', + name=3D'project', + ), + migrations.DeleteModel( + name=3D'MessageProperty', + ), + migrations.DeleteModel( + name=3D'ProjectProperty', + ), + ] diff --git a/api/models.py b/api/models.py index 2a6c09b..62f2656 100644 --- a/api/models.py +++ b/api/models.py @@ -304,16 +304,6 @@ class ProjectResult(Result): return self.project =20 =20 -class ProjectProperty(models.Model): - project =3D models.ForeignKey('Project', on_delete=3Dmodels.CASCADE) - name =3D models.CharField(max_length=3D1024, db_index=3DTrue) - value =3D jsonfield.JSONField() - - class Meta: - unique_together =3D ('project', 'name',) - verbose_name_plural =3D "Project Properties" - - declare_event("SeriesComplete", project=3D"project object", series=3D"series instance that is marked complete") declare_event("SeriesMerged", project=3D"project object", @@ -358,7 +348,7 @@ class MessageManager(models.Manager): return None else: q =3D super(MessageManager, self).get_queryset() - return q.filter(is_series_head=3DTrue).prefetch_related('messagepr= operty_set', 'project') + return q.filter(is_series_head=3DTrue).prefetch_related('project') =20 def find_series(self, message_id, project_name=3DNone): heads =3D self.series_heads(project_name) @@ -795,23 +785,6 @@ class MessageResult(Result): return self.message =20 =20 -class MessageProperty(models.Model): - message =3D models.ForeignKey('Message', on_delete=3Dmodels.CASCADE) - name =3D models.CharField(max_length=3D256) - value =3D jsonfield.JSONField() - - def __str__(self): - if len(self.value) > 30: - val_prev =3D self.value[:30] + "..." - else: - val_prev =3D self.value - return "%s: %s =3D %s" % (self.message.subject, self.name, val_pre= v) - - class Meta: - unique_together =3D ('message', 'name',) - verbose_name_plural =3D "Message Properties" - - class Module(models.Model): """ Module information """ name =3D models.CharField(max_length=3D128, unique=3DTrue) diff --git a/api/search.py b/api/search.py index 1c5aaaf..658ad4a 100644 --- a/api/search.py +++ b/api/search.py @@ -8,7 +8,7 @@ # This work is licensed under the MIT License. Please see the LICENSE fil= e or # http://opensource.org/licenses/MIT. =20 -from .models import Message, MessageProperty, MessageResult, Result, Queue= dSeries +from .models import Message, MessageResult, Result, QueuedSeries from functools import reduce =20 from django.db import connection diff --git a/mods/git.py b/mods/git.py index 8e868c2..c1909ab 100644 --- a/mods/git.py +++ b/mods/git.py @@ -20,8 +20,7 @@ from django.utils.html import format_html from django.db.models import Q from mod import PatchewModule from event import declare_event, register_handler, emit_event -from api.models import (Message, MessageProperty, Project, - ProjectProperty, Result) +from api.models import (Message, Project, Result) from api.rest import PluginMethodField, reverse_detail from api.views import APILoginRequiredView, prepare_series from patchew.logviewer import LogView diff --git a/mods/testing.py b/mods/testing.py index e5562ba..f238406 100644 --- a/mods/testing.py +++ b/mods/testing.py @@ -19,8 +19,7 @@ import datetime import time import math from api.views import APILoginRequiredView -from api.models import (Message, MessageProperty, MessageResult, - Project, ProjectResult, Result) +from api.models import (Message, MessageResult, Project, ProjectResult, Re= sult) from api.rest import PluginMethodField, reverse_detail from api.search import SearchEngine, FLAG_TESTED from event import emit_event, declare_event, register_handler --=20 2.21.0 _______________________________________________ Patchew-devel mailing list Patchew-devel@redhat.com https://www.redhat.com/mailman/listinfo/patchew-devel