get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/1516053/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "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-          &lt;<a href=\"mailto:{{ maintainer.email }}\">{{ maintainer.email }}</a>&gt;\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"
    ]
}