Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/1516053/?format=api
{ "id": 1516053, "url": "http://patchwork.ozlabs.org/api/patches/1516053/?format=api", "web_url": "http://patchwork.ozlabs.org/project/patchwork/patch/20210811213705.36293-17-stephen@that.guru/", "project": { "id": 16, "url": "http://patchwork.ozlabs.org/api/projects/16/?format=api", "name": "Patchwork", "link_name": "patchwork", "list_id": "patchwork.lists.ozlabs.org", "list_email": "patchwork@lists.ozlabs.org", "web_url": "http://jk.ozlabs.org/projects/patchwork/", "scm_url": "git://github.com/getpatchwork/patchwork", "webscm_url": "https://github.com/getpatchwork/patchwork", "list_archive_url": "", "list_archive_url_format": "", "commit_url_format": "" }, "msgid": "<20210811213705.36293-17-stephen@that.guru>", "list_archive_url": null, "date": "2021-08-11T21:37:02", "name": "[RFC,16/19] templates: Convert project view", "commit_ref": null, "pull_url": null, "state": "rfc", "archived": false, "hash": "5f2b65f2dbe068458991aec6de3e34eb4869ac2a", "submitter": { "id": 69991, "url": "http://patchwork.ozlabs.org/api/people/69991/?format=api", "name": "Stephen Finucane", "email": "stephen@that.guru" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/patchwork/patch/20210811213705.36293-17-stephen@that.guru/mbox/", "series": [ { "id": 257699, "url": "http://patchwork.ozlabs.org/api/series/257699/?format=api", "web_url": "http://patchwork.ozlabs.org/project/patchwork/list/?series=257699", "date": "2021-08-11T21:36:49", "name": "Integrate Bulma", "version": 1, "mbox": "http://patchwork.ozlabs.org/series/257699/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/1516053/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/1516053/checks/", "tags": {}, "related": [ { "id": 1523346, "url": "http://patchwork.ozlabs.org/api/patches/1523346/?format=api", "web_url": "http://patchwork.ozlabs.org/project/patchwork/patch/20210901165756.181192-17-stephen@that.guru/", "msgid": "<20210901165756.181192-17-stephen@that.guru>", "list_archive_url": null, "date": "2021-09-01T16:57:53", "name": "[RFC,v2,16/19] templates: Convert project view", "mbox": "http://patchwork.ozlabs.org/project/patchwork/patch/20210901165756.181192-17-stephen@that.guru/mbox/" } ], "headers": { "Return-Path": "\n <patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org>", "X-Original-To": [ "incoming@patchwork.ozlabs.org", "patchwork@lists.ozlabs.org" ], "Delivered-To": [ "patchwork-incoming@bilbo.ozlabs.org", "patchwork@lists.ozlabs.org" ], "Authentication-Results": [ "ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=lists.ozlabs.org\n (client-ip=2404:9400:2:0:216:3eff:fee1:b9f1; helo=lists.ozlabs.org;\n envelope-from=patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org;\n receiver=<UNKNOWN>)", "ozlabs.org;\n\tdkim=fail reason=\"key not found in DNS\" header.d=that.guru\n header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=ZRyPifiB;\n\tdkim-atps=neutral", "lists.ozlabs.org;\n\tdkim=fail reason=\"key not found in DNS\" header.d=that.guru\n header.i=@that.guru header.a=rsa-sha256 header.s=x header.b=ZRyPifiB;\n\tdkim-atps=neutral", "lists.ozlabs.org;\n spf=none (no SPF record) smtp.mailfrom=that.guru\n (client-ip=136.175.108.10; helo=mail-108-mta10.mxroute.com;\n envelope-from=stephen@that.guru; receiver=<UNKNOWN>)", "lists.ozlabs.org;\n dkim=fail reason=\"key not found in DNS\" header.d=that.guru\n header.i=@that.guru\n header.a=rsa-sha256 header.s=x header.b=ZRyPifiB;\n dkim-atps=neutral" ], "Received": [ "from lists.ozlabs.org (lists.ozlabs.org\n [IPv6:2404:9400:2:0:216:3eff:fee1:b9f1])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange X25519 server-signature RSA-PSS (4096 bits))\n\t(No client certificate requested)\n\tby ozlabs.org (Postfix) with ESMTPS id 4GlPCt00Sbz9sSs\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 12 Aug 2021 08:11:05 +1000 (AEST)", "from boromir.ozlabs.org (localhost [IPv6:::1])\n\tby lists.ozlabs.org (Postfix) with ESMTP id 4GlPCs6CXNz3cQ8\n\tfor <incoming@patchwork.ozlabs.org>; Thu, 12 Aug 2021 08:11:05 +1000 (AEST)", "from mail-108-mta10.mxroute.com (mail-108-mta10.mxroute.com\n [136.175.108.10])\n (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits))\n (No client certificate requested)\n by lists.ozlabs.org (Postfix) with ESMTPS id 4GlPBz6BtGz304Q\n for <patchwork@lists.ozlabs.org>; Thu, 12 Aug 2021 08:10:19 +1000 (AEST)", "from filter004.mxroute.com ([149.28.56.236] filter004.mxroute.com)\n (Authenticated sender: mN4UYu2MZsgR)\n by mail-108-mta10.mxroute.com (ZoneMTA) with ESMTPSA id\n 17b37440cdc00074ba.002\n for <patchwork@lists.ozlabs.org>\n (version=TLSv1/SSLv3 cipher=ECDHE-RSA-AES128-GCM-SHA256);\n Wed, 11 Aug 2021 22:10:11 +0000" ], "X-Zone-Loop": "f033efdf7d478c5cb551d60fb0457d880cc5758542b7", "X-Originating-IP": "[149.28.56.236]", "DKIM-Signature": "v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=that.guru;\n s=x;\n h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:\n Message-Id:Date:Subject:Cc:To:From:Sender:Reply-To:Content-Type:Content-ID:\n Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc\n :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe:\n List-Post:List-Owner:List-Archive;\n bh=XvSUNYliikOU+/WAJFFcEscdSbGuOYxe8OAUUT8bkxc=; b=ZRyPifiBdlXDZey5i1iQ7FZJfH\n ibAxCttPd0RWEfcFg44bcK6Za5itlXXEOi+LdQ15CoJZWHTPm1/waUS3Z/nGgbF9sWZYR+4sMqAgM\n t7rQzL60wwui16Bdcw1gIXuC3ynjz7YDUfdPKrni6qccHzWtYLbEcNCF1U4MW6V17eYVgM3me8nFl\n 4uT5TSKDyKYpWMV8C1PyxHO6LYC0LR/7xjDUFmjfowOAnrSY3IjchPakjO+lrCfVFsllhQ02fkDCN\n ysxUnl3C8ESh/zdV0mNf0ijLfAY14I0maSUXLUWm665xuYV6Alhrp452cT8/BPwFrdy3rS5EF54Uw\n otp6nasw==;", "From": "Stephen Finucane <stephen@that.guru>", "To": "patchwork@lists.ozlabs.org", "Subject": "[RFC PATCH 16/19] templates: Convert project view", "Date": "Wed, 11 Aug 2021 22:37:02 +0100", "Message-Id": "<20210811213705.36293-17-stephen@that.guru>", "X-Mailer": "git-send-email 2.31.1", "In-Reply-To": "<20210811213705.36293-1-stephen@that.guru>", "References": "<20210811213705.36293-1-stephen@that.guru>", "MIME-Version": "1.0", "X-AuthUser": "stephen@that.guru", "X-Zone-Spam-Resolution": "no action", "X-Zone-Spam-Status": "No, score=5, required=15, tests=[ARC_NA=0,\n MID_CONTAINS_FROM=1, FROM_HAS_DN=0, RCPT_COUNT_THREE=0, TO_DN_SOME=0,\n R_MISSING_CHARSET=2.5, RCVD_COUNT_ZERO=0, FROM_EQ_ENVFROM=0, MIME_TRACE=0,\n BROKEN_CONTENT_TYPE=1.5, NEURAL_SPAM=0]", "X-BeenThere": "patchwork@lists.ozlabs.org", "X-Mailman-Version": "2.1.29", "Precedence": "list", "List-Id": "Patchwork development <patchwork.lists.ozlabs.org>", "List-Unsubscribe": "<https://lists.ozlabs.org/options/patchwork>,\n <mailto:patchwork-request@lists.ozlabs.org?subject=unsubscribe>", "List-Archive": "<http://lists.ozlabs.org/pipermail/patchwork/>", "List-Post": "<mailto:patchwork@lists.ozlabs.org>", "List-Help": "<mailto:patchwork-request@lists.ozlabs.org?subject=help>", "List-Subscribe": "<https://lists.ozlabs.org/listinfo/patchwork>,\n <mailto:patchwork-request@lists.ozlabs.org?subject=subscribe>", "Content-Type": "text/plain; charset=\"us-ascii\"", "Content-Transfer-Encoding": "7bit", "Errors-To": "patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org", "Sender": "\"Patchwork\"\n <patchwork-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org>" }, "content": "Signed-off-by: Stephen Finucane <stephen@that.guru>\n---\n patchwork/forms.py | 83 +++++\n patchwork/templates/patchwork/project.html | 407 +++++++++++++++++----\n patchwork/views/project.py | 81 +++-\n 3 files changed, 500 insertions(+), 71 deletions(-)", "diff": "diff --git patchwork/forms.py patchwork/forms.py\nindex 5f8dff96..a975db18 100644\n--- patchwork/forms.py\n+++ patchwork/forms.py\n@@ -244,6 +244,89 @@ class UserProfileForm(forms.ModelForm):\n labels = {'show_ids': 'Show Patch IDs:'}\n \n \n+class AddProjectMaintainerForm(forms.Form):\n+\n+ name = 'add-maintainer'\n+\n+ username = forms.RegexField(\n+ regex=r'^\\w+$', max_length=30, label='Username'\n+ )\n+\n+ def __init__(self, project, *args, **kwargs):\n+ self.project = project\n+ super().__init__(*args, **kwargs)\n+\n+ def clean_username(self):\n+ value = self.cleaned_data['username']\n+\n+ try:\n+ user = User.objects.get(username__iexact=value)\n+ except User.DoesNotExist:\n+ raise forms.ValidationError(\n+ 'That username is not valid. Please choose another.'\n+ )\n+\n+ if self.project in user.profile.maintainer_projects.all():\n+ raise forms.ValidationError(\n+ 'That user is already a maintainer of this project.'\n+ )\n+\n+ return value\n+\n+\n+class RemoveProjectMaintainerForm(forms.Form):\n+\n+ name = 'remove-maintainer'\n+\n+ username = forms.RegexField(\n+ regex=r'^\\w+$', max_length=30, label='Username'\n+ )\n+\n+ def __init__(self, project, *args, **kwargs):\n+ self.project = project\n+ super().__init__(*args, **kwargs)\n+\n+ def clean_username(self):\n+ value = self.cleaned_data['username']\n+\n+ try:\n+ user = User.objects.get(username__iexact=value)\n+ except User.DoesNotExist:\n+ raise forms.ValidationError(\n+ 'That username is not valid. Please choose another.'\n+ )\n+\n+ maintainers = User.objects.filter(\n+ profile__maintainer_projects=self.project,\n+ ).select_related('profile')\n+\n+ if user not in maintainers:\n+ raise forms.ValidationError(\n+ 'That user is not a maintainer of this project.'\n+ )\n+\n+ # TODO(stephenfin): Should we prevent users removing themselves?\n+\n+ if maintainers.count() <= 1:\n+ raise forms.ValidationError(\n+ 'You cannot remove the only maintainer of the project.'\n+ )\n+\n+ return value\n+\n+\n+class ProjectSettingsForm(forms.ModelForm):\n+\n+ name = 'project-settings'\n+\n+ class Meta:\n+ model = models.Project\n+ fields = [\n+ 'name', 'web_url', 'scm_url', 'webscm_url', 'list_archive_url',\n+ 'list_archive_url_format', 'commit_url_format',\n+ ]\n+\n+\n def _get_delegate_qs(project, instance=None):\n if instance and not project:\n project = instance.project\ndiff --git patchwork/templates/patchwork/project.html patchwork/templates/patchwork/project.html\nindex cad372f7..1b25bbe6 100644\n--- patchwork/templates/patchwork/project.html\n+++ patchwork/templates/patchwork/project.html\n@@ -1,79 +1,348 @@\n-{% extends \"base.html\" %}\n+{% extends \"base2.html\" %}\n \n {% block title %}{{ project.name }}{% endblock %}\n-{% block info_active %}active{% endblock %}\n \n {% block body %}\n-<h1>About {{ project.name }}</h1>\n-\n-<table class=\"horizontal\">\n- <tr>\n- <th>Name</th>\n- <td>{{ project.name }}\n- </tr>\n- <tr>\n- <th>List address</th>\n- <td>{{ project.listemail }}</td>\n- </tr>\n-{% if project.list_archive_url %}\n- <tr>\n- <th>List archive</th>\n- <td><a href=\"{{ project.list_archive_url }}\">{{ project.list_archive_url }}</a></td>\n- </tr>\n+{% for message in messages %}\n+{% if message.tags == 'success' %}\n+<div class=\"notification is-success\">\n+{% elif message.tags == 'warning' %}\n+<div class=\"notification is-warning\">\n+{% elif message.tags == 'error' %}\n+<div class=\"notification is-danger\">\n+{% else %}\n+<div class=\"notification\">\n {% endif %}\n- <tr>\n- <th>Maintainer{{ maintainers|length|pluralize }}</th>\n- <td>\n- {% for maintainer in maintainers %}\n- {{ maintainer.profile.name }}\n- <<a href=\"mailto:{{ maintainer.email }}\">{{ maintainer.email }}</a>>\n- <br />\n- {% endfor %}\n- </td>\n- </tr>\n- <tr>\n- <th>Patches </th>\n- <td>{{ n_patches }} (+ {{ n_archived_patches }} archived)</td>\n- </tr>\n-{% if project.web_url %}\n- <tr>\n- <th>Website</th>\n- <td><a href=\"{{ project.web_url }}\">{{ project.web_url }}</a></td>\n- </tr>\n+ {{ message }}\n+ <button class=\"delete\" onclick=\"dismiss(this);\"></button>\n+</div>\n+{% endfor %}\n+\n+<div class=\"container\" style=\"margin-top: 1rem;\">\n+ <section class=\"block\">\n+ <h1 class=\"title\">\n+ About {{ project.name }}\n+ </h1>\n+ <p class=\"subtitle\">\n+ {{ project.listemail }}\n+ </p>\n+ </section>\n+\n+ <div class=\"block\"></div>\n+\n+ <section class=\"block\">\n+ <h2 id=\"overview\" class=\"title is-4\">\n+ <a href=\"#overview\" title=\"Permalink to this section\">#</a>\n+ Overview\n+ </h2>\n+\n+ <div class=\"tile is-ancestor has-text-centered\">\n+ <div class=\"tile is-parent\">\n+ <div class=\"tile is-child is-primary box\">\n+ <p class=\"title\">\n+ {{ n_patches }}\n+ </p>\n+ <p class=\"subtitle\">Patches</p>\n+ </div>\n+ </div>\n+\n+ <div class=\"tile is-parent\">\n+ <a href=\"{{ project.web_url }}\" class=\"tile is-child box\">\n+ <p class=\"title\">\n+ <span class=\"icon\">\n+ <i class=\"fas fa-home\"></i>\n+ </span>\n+ </p>\n+ <p class=\"subtitle\">Website</p>\n+ </a>\n+ </div>\n+\n+ <div class=\"tile is-parent\">\n+ <a href=\"{{ project.list_archive_url }}\" class=\"tile is-child box\">\n+ <p class=\"title\">\n+ <span class=\"icon\">\n+ <i class=\"fas fa-envelope\"></i>\n+ </span>\n+ </p>\n+ <p class=\"subtitle\">List Archives</p>\n+ </a>\n+ </div>\n+\n+ <div class=\"tile is-parent\">\n+ <a href=\"{{ project.webscm_url }}\" class=\"tile is-child box\">\n+ <p class=\"title\">\n+ <span class=\"icon\">\n+ <i class=\"fas fa-code\"></i>\n+ </span>\n+ </p>\n+ <p class=\"subtitle\">Source Code</p>\n+ </a>\n+ </div>\n+ </div>\n+ </section>\n+\n+ <section class=\"block\">\n+ <h2 id=\"maintainers\" class=\"title is-4\">\n+ <a href=\"#maintainers\" title=\"Permalink to this section\">#</a>\n+ Maintainers\n+ </h2>\n+\n+{% for maintainer in maintainers %}\n+ <div class=\"card\">\n+ <div class=\"card-content\">\n+ <div class=\"columns\">\n+ <div class=\"column\">\n+ <span class=\"has-text-weight-bold\">{{ maintainer.username }}</span>\n+{% if maintainer.first_name and maintainer.last_name %}\n+ ({{ maintainer.first_name }} {{ maintainer.last_name }})\n+{% elif maintainer.first_name %}\n+ ({{ maintainer.first_name }})\n+{% elif maintainer.last_name %}\n+ ({{ maintainer.last_name }})\n+{% endif %}\n+ </div>\n+ <div class=\"column\">\n+ <span>{{ maintainer.email }}</span>\n+ </div>\n+{% if maintainers|length > 1 and maintainer.username != user.username %}\n+ <div class=\"column is-narrow\">\n+ <form method=\"post\">\n+ {% csrf_token %}\n+ <input type=\"hidden\" name=\"form_name\" value=\"remove-maintainer\">\n+ <input type=\"hidden\" name=\"email\" value=\"{{ maintainer.username }}\">\n+ <span class=\"icon\">\n+ <i class=\"fas fa-trash\"></i>\n+ </span>\n+ </form>\n+ </div>\n {% endif %}\n-{% if project.webscm_url %}\n- <tr>\n- <th>Source Code Web Interface</th>\n- <td><a href=\"{{ project.webscm_url }}\">{{ project.webscm_url }}</a></td>\n- </tr>\n+ </div>\n+ </div>\n+ </div>\n+{% empty %}\n+ <p>This project has no maintainers.</p>\n+{% endfor %}\n+{% if project in user.profile.maintainer_projects.all %}\n+ <div class=\"block\"></div>\n+ <div class=\"block\">\n+ <form class=\"block\" method=\"post\">\n+ {% csrf_token %}\n+ <input type=\"hidden\" name=\"form_name\" value=\"add-maintainer\">\n+ <label for=\"id_username\" class=\"label\">\n+ Add maintainer\n+ </label>\n+ <div class=\"field is-grouped\">\n+ <div class=\"control\">\n+ <input id=\"id_username\" type=\"text\" name=\"username\" placeholder=\"e.g. bobsmith\" class=\"input\" value=\"{{ add_maintainer_form.username.value|default:'' }}\" required>\n+{% for error in add_maintainer_form.username.errors %}\n+ <p class=\"help is-danger\">{{ error }}</p>\n+{% endfor %}\n+ </div>\n+ <div class=\"control\">\n+ <button class=\"button is-info\">\n+ Add maintainer\n+ </button>\n+ </div>\n+ </div>\n+ </form>\n+ </div>\n {% endif %}\n-{% if project.scm_url %}\n- <tr>\n- <th>Source Code Manager URL</th>\n- <td><a href=\"{{ project.scm_url }}\">{{ project.scm_url }}</a></td>\n- </tr>\n+ </section>\n+\n+{% if pwclientrc %}\n+ <section class=\"block\">\n+ <h2 id=\"pwclient\" class=\"title is-4\">\n+ <a href=\"#pwclient\" title=\"Permalink to this section\">#</a>\n+ <code>pwclientrc</code> configuration\n+ </h2>\n+\n+ <div class=\"content\">\n+ <p>\n+ <code>pwclient</code> is the command-line client for Patchwork. Currently,\n+ it provides access to some read-only features of Patchwork, such as\n+ downloading and applying patches.\n+ </p>\n+\n+ <p>To use pwclient, you will need:</p>\n+\n+ <ul>\n+ <li>\n+ The <a href=\"https://github.com/getpatchwork/pwclient\">pwclient</a>\n+ program.\n+ </li>\n+ <li>\n+ (Optional) A <code>.pwclientrc</code> file for this project,\n+ which should be stored in your home directory.\n+ </li>\n+ </ul>\n+\n+ <p>A sample <code>pwclientrc</code> config file is provided below.</p>\n+\n+ <pre><code>{{ pwclientrc }}</code></pre>\n+ </div>\n+ </section>\n {% endif %}\n-</table>\n-\n-{% if enable_xmlrpc %}\n-<h2>pwclient</h2>\n-\n-<p>\n- <code>pwclient</code> is the command-line client for Patchwork. Currently,\n- it provides access to some read-only features of Patchwork, such as\n- downloading and applying patches.\n-</p>\n-\n-<p>To use pwclient, you will need:</p>\n-<ul>\n- <li>\n- The <a href=\"https://github.com/getpatchwork/pwclient\">pwclient</a>\n- program.\n- </li>\n- <li>\n- (Optional) A <code><a href=\"{% url 'pwclientrc' project.linkname %}\">.pwclientrc</a></code>\n- file for this project, which should be stored in your home directory.\n- </li>\n-</ul>\n+\n+{% if project in user.profile.maintainer_projects.all %}\n+ <section class=\"block\">\n+ <h2 id=\"settings\" class=\"title is-4\">\n+ <a href=\"#settings\" title=\"Permalink to this section\">#</a>\n+ Settings\n+ </h2>\n+\n+{% if project_settings_form.non_field_errors %}\n+ <div class=\"notification is-danger is-light\">\n+ <button class=\"delete\" onclick=\"dismiss(this);\"></button>\n+ {{ project_settings_form.non_field_errors }}\n+ </div>\n {% endif %}\n+ <form class=\"block\" method=\"post\">\n+ {% csrf_token %}\n+ <input type=\"hidden\" name=\"form_name\" value=\"project-settings\">\n+ <div class=\"field\">\n+ <label for=\"linkname\" class=\"label\">\n+ Linkname\n+ </label>\n+ <div class=\"control\">\n+ <input id=\"id_linkname\" type=\"text\" name=\"linkname\" class=\"input\" value=\"{{ project.linkname }}\" disabled>\n+ </div>\n+ <p class=\"help\">\n+ Patchwork project ID.\n+ </p>\n+ </div>\n+ <div class=\"field\">\n+ <label for=\"listemail\" class=\"label\">\n+ List email\n+ </label>\n+ <div class=\"control\">\n+ <input id=\"id_listemail\" type=\"text\" name=\"listemail\" class=\"input\" value=\"{{ project.listemail }}\" disabled>\n+ </div>\n+ <p class=\"help\">\n+ Mailing list email.\n+ </p>\n+ </div>\n+ <div class=\"field\">\n+ <label for=\"listid\" class=\"label\">\n+ List ID\n+ </label>\n+ <div class=\"control\">\n+ <input id=\"id_listid\" type=\"text\" name=\"listid\" class=\"input\" value=\"{{ project.listid }}\" disabled>\n+ </div>\n+ <p class=\"help\">\n+ Mailing list ID.\n+ </p>\n+ </div>\n+ <div class=\"field\">\n+ <label for=\"name\" class=\"label\">\n+ Name\n+ </label>\n+ <div class=\"control\">\n+ <input id=\"id_name\" type=\"text\" name=\"name\" class=\"input\" value=\"{{ project.name }}\" required>\n+ </div>\n+ <p class=\"help\">\n+ Name of project.\n+ </p>\n+{% for error in project_settings_form.name.errors %}\n+ <p class=\"help is-danger\">{{ error }}</p>\n+{% endfor %}\n+ </div>\n+ <div class=\"field\">\n+ <label for=\"web_url\" class=\"label\">\n+ Website\n+ </label>\n+ <div class=\"control\">\n+ <input id=\"id_web_url\" type=\"text\" name=\"web_url\" class=\"input\" value=\"{{ project.web_url }}\">\n+ </div>\n+ <p class=\"help\">\n+ Homepage of project.\n+ </p>\n+{% for error in project_settings_form.web_url.errors %}\n+ <p class=\"help is-danger\">{{ error }}</p>\n+{% endfor %}\n+ </div>\n+ <div class=\"field\">\n+ <label for=\"scm_url\" class=\"label\">\n+ Source code manager URL\n+ </label>\n+ <div class=\"control\">\n+ <input id=\"id_scm_url\" type=\"text\" name=\"scm_url\" class=\"input\" value=\"{{ project.scm_url }}\">\n+ </div>\n+ <p class=\"help\">\n+ Checkout or clone URL for project source code.\n+ </p>\n+{% for error in project_settings_form.scm_url.errors %}\n+ <p class=\"help is-danger\">{{ error }}</p>\n+{% endfor %}\n+ </div>\n+ <div class=\"field\">\n+ <label for=\"webscm_url\" class=\"label\">\n+ Source code website\n+ </label>\n+ <div class=\"control\">\n+ <input id=\"id_webscm_url\" type=\"text\" name=\"webscm_url\" class=\"input\" value=\"{{ project.webscm_url }}\">\n+ </div>\n+ <p class=\"help\">\n+ Website for browing project source code.\n+ </p>\n+{% for error in project_settings_form.webscm_url.errors %}\n+ <p class=\"help is-danger\">{{ error }}</p>\n+{% endfor %}\n+ </div>\n+ <div class=\"field\">\n+ <label for=\"list_archive_url\" class=\"label\">\n+ List archives\n+ </label>\n+ <div class=\"control\">\n+ <input id=\"id_list_archive_url\" type=\"text\" name=\"list_archive_url\" class=\"input\" value=\"{{ project.list_archive_url }}\">\n+ </div>\n+ <p class=\"help\">\n+ URL for accessing list archives.\n+ </p>\n+{% for error in project_settings_form.list_archive_url.errors %}\n+ <p class=\"help is-danger\">{{ error }}</p>\n+{% endfor %}\n+ </div>\n+ <div class=\"field\">\n+ <label for=\"list_archive_url_format\" class=\"label\">\n+ List archive URL format\n+ </label>\n+ <div class=\"control\">\n+ <input id=\"id_list_archive_url_format\" type=\"text\" name=\"list_archive_url_format\" class=\"input\" value=\"{{ project.list_archive_url_format }}\">\n+ </div>\n+ <p class=\"help\">\n+ URL format for the list archive's Message-ID redirector.\n+ <code>{}</code> will be replaced by the Message-ID.\n+ </p>\n+{% for error in project_settings_form.list_archive_url_format.errors %}\n+ <p class=\"help is-danger\">{{ error }}</p>\n+{% endfor %}\n+ </div>\n+ <div class=\"field\">\n+ <label for=\"commit_url_format\" class=\"label\">\n+ Commit URL format\n+ </label>\n+ <div class=\"control\">\n+ <input id=\"id_commit_url_format\" type=\"text\" name=\"commit_url_format\" class=\"input\" value=\"{{ project.commit_url_format }}\">\n+ </div>\n+ <p class=\"help\">\n+ URL format for a particular commit.\n+ <code>{}</code> will be replaced by the commit SHA.\n+ </p>\n+{% for error in project_settings_form.commit_url_format.errors %}\n+ <p class=\"help is-danger\">{{ error }}</p>\n+{% endfor %}\n+ </div>\n+ <div class=\"control\">\n+ <button class=\"button is-primary is-disabled\">Update settings</button>\n+ </div>\n+ </form>\n+ </section>\n+{% endif %}\n+</div>\n+\n+<script>\n+function dismiss(el){\n+ el.parentNode.style.display = 'none';\n+};\n+</script>\n {% endblock %}\ndiff --git patchwork/views/project.py patchwork/views/project.py\nindex a993618a..788662fb 100644\n--- patchwork/views/project.py\n+++ patchwork/views/project.py\n@@ -5,11 +5,15 @@\n \n from django.conf import settings\n from django.contrib.auth.models import User\n+from django.contrib import messages\n+from django.contrib.sites.shortcuts import get_current_site\n from django.http import HttpResponseRedirect\n from django.shortcuts import get_object_or_404\n from django.shortcuts import render\n+from django.template.loader import render_to_string\n from django.urls import reverse\n \n+from patchwork import forms\n from patchwork.models import Patch\n from patchwork.models import Project\n \n@@ -32,13 +36,86 @@ def project_detail(request, project_id):\n project = get_object_or_404(Project, linkname=project_id)\n patches = Patch.objects.filter(project=project)\n \n+ add_maintainer_form = forms.AddProjectMaintainerForm(project),\n+ remove_maintainer_form = forms.RemoveProjectMaintainerForm(project)\n+ project_settings_form = forms.ProjectSettingsForm(instance=project)\n+\n+ if request.method == 'POST':\n+ form_name = request.POST.get('form_name', '')\n+ if form_name == forms.AddProjectMaintainerForm.name:\n+ add_maintainer_form = forms.AddProjectMaintainerForm(\n+ project, data=request.POST)\n+ if add_maintainer_form.is_valid():\n+ messages.success(\n+ request,\n+ 'Added new maintainer.',\n+ )\n+ return HttpResponseRedirect(\n+ reverse(\n+ 'project-detail',\n+ kwargs={'project_id': project.linkname},\n+ ),\n+ )\n+ messages.error(request, 'Error adding project maintainer.')\n+ elif form_name == forms.RemoveProjectMaintainerForm.name:\n+ remove_maintainer_form = forms.RemoveProjectMaintainerForm(\n+ project, data=request.POST)\n+ if remove_maintainer_form.is_valid():\n+ messages.success(\n+ request,\n+ 'Removed maintainer.',\n+ )\n+ return HttpResponseRedirect(\n+ reverse(\n+ 'project-detail',\n+ kwargs={'project_id': project.linkname},\n+ ),\n+ )\n+ messages.error(request, 'Error removing project maintainer.')\n+ elif form_name == forms.ProjectSettingsForm.name:\n+ project_settings_form = forms.ProjectSettingsForm(\n+ instance=project, data=request.POST)\n+ if project_settings_form.is_valid():\n+ project_settings_form.save()\n+ messages.success(\n+ request,\n+ 'Updated project settings.',\n+ )\n+ return HttpResponseRedirect(\n+ reverse(\n+ 'project-detail',\n+ kwargs={'project_id': project.linkname},\n+ ),\n+ )\n+ messages.error(request, 'Error updating project settings.')\n+ else:\n+ messages.error(request, 'Unrecognized request')\n+\n context = {\n 'project': project,\n 'maintainers': User.objects.filter(\n profile__maintainer_projects=project\n ).select_related('profile'),\n 'n_patches': patches.filter(archived=False).count(),\n- 'n_archived_patches': patches.filter(archived=True).count(),\n- 'enable_xmlrpc': settings.ENABLE_XMLRPC,\n+ 'add_maintainer_form': add_maintainer_form,\n+ 'remove_maintainer_form': remove_maintainer_form,\n+ 'project_settings_form': project_settings_form,\n }\n+\n+ if settings.ENABLE_XMLRPC:\n+ if settings.FORCE_HTTPS_LINKS or request.is_secure():\n+ scheme = 'https'\n+ else:\n+ scheme = 'http'\n+\n+ context['pwclientrc'] = render_to_string(\n+ 'patchwork/pwclientrc',\n+ {\n+ 'project': project,\n+ 'scheme': scheme,\n+ 'user': request.user,\n+ 'site': get_current_site(request),\n+ },\n+ ).strip()\n+\n return render(request, 'patchwork/project.html', context)\n", "prefixes": [ "RFC", "16/19" ] }