[RFC PATCH 10/19] templates: Convert user profile view

Stephen Finucane stephen at that.guru
Thu Aug 12 07:36:56 AEST 2021


This is our first "full page", in that we have a navbar. A large number
of TODOs are left here as we're simply changing templates rather than
updating views, but those gaps will be closed in a future change.

Signed-off-by: Stephen Finucane <stephen at that.guru>
---
 patchwork/templates/patchwork/login.html      |   5 +-
 patchwork/templates/patchwork/profile.html    | 432 ++++++++++++------
 templates/base2.html                          |  82 ++++
 .../registration/password_reset_confirm.html  |   4 +
 .../registration/password_reset_done.html     |   4 +
 .../registration/password_reset_form.html     |   4 +
 6 files changed, 395 insertions(+), 136 deletions(-)

diff --git patchwork/templates/patchwork/login.html patchwork/templates/patchwork/login.html
index ef609f1f..b8ab462c 100644
--- patchwork/templates/patchwork/login.html
+++ patchwork/templates/patchwork/login.html
@@ -2,8 +2,7 @@
 
 {% block title %}Sign in to Patchwork{% endblock %}
 
-{% block headers %}
-{% endblock %}
+{% block navigation %}{% endblock %}
 
 {% block body %}
 <section class="hero is-primary is-fullheight">
@@ -75,3 +74,5 @@ for (var i = 0; i < btns.length; i++) {
 }
 </script>
 {% endblock %}
+
+{% block footer %}{% endblock %}
diff --git patchwork/templates/patchwork/profile.html patchwork/templates/patchwork/profile.html
index 552dde47..7a0b54fe 100644
--- patchwork/templates/patchwork/profile.html
+++ patchwork/templates/patchwork/profile.html
@@ -1,173 +1,337 @@
-{% extends "base.html" %}
+{% extends "base2.html" %}
 
 {% block title %}{{ user.username }}{% endblock %}
-{% block heading %}Your Profile{% endblock %}
 
 {% block body %}
-<h1>Your Profile</h1>
+<div class="container" style="margin-top: 1rem;">
+  <div class="columns">
+    <div class="column is-3">
+      <aside class="menu">
+        <p class="menu-label">
+          Overview
+        </p>
+        <ul class="menu-list">
+          <li><a href="#projects">Projects</a></li>
+          <li><a href="#bundles">Bundles</a></li>
+          <li><a href="#todo">Todo List</a></li>
+        </ul>
+        <p class="menu-label">
+          Settings
+        </p>
+        <ul class="menu-list">
+          <li><a href="#profile">Profile</a></li>
+          <li><a href="#linked-emails">Linked emails</a></li>
+          <li><a href="#profile-settings">Profile settings</a></li>
+          <li><a href="#security">Security</a></li>
+        </ul>
+      </aside>
+    </div>
 
+    <div class="column is-9">
+      <h1 id="overview" class="title">
+        <a href="#overview" title="Permalink to this section"></a>
+        Overview
+      </h1>
+
+      <section class="block">
+        <h2 id="projects" class="title is-4">
+          <a href="#projects" title="Permalink to this section">#</a>
+          Projects
+        </h2>
 {% if user.profile.maintainer_projects.count %}
-<p>
-  Maintainer of
+        <p>
+          Maintainer of
 {% for project in user.profile.maintainer_projects.all %}
-  <a href="{% url 'patch-list' project_id=project.linkname %}">{{ project.linkname }}</a>{% if not forloop.last %},{% endif %}
+            <a href="{% url 'patch-list' project_id=project.linkname %}">{{ project.linkname }}</a>{% if not forloop.last %},{% endif %}
 {% endfor %}.
-</p>
+        </p>
 {% endif %}
-
 {% if user.profile.contributor_projects.count %}
-<p>
-  Contributor to
+        <p>
+          Contributor to
 {% for project in user.profile.contributor_projects.all %}
-  <a href="{% url 'patch-list' project_id=project.linkname %}">{{ project.linkname }}</a>{% if not forloop.last %},{% endif %}
+          <a href="{% url 'patch-list' project_id=project.linkname %}">{{ project.linkname }}</a>{% if not forloop.last %},{% endif %}
 {% endfor %}.
-</p>
+        </p>
 {% endif %}
+      </section>
 
-<div class="leftcol">
-  <div class="box">
-    <h2>Todo</h2>
-    <p>
-      Your <a href="{% url 'user-todos' %}">todo list</a> contains patches that
-      have been delegated to you.
+      <section class="block">
+        <h2 id="bundles" class="title is-4">
+          <a href="#bundles" title="Permalink to this section">#</a>
+          Bundles
+        </h2>
+{% if bundles %}
+        <p>You have the following bundle{{ bundles|length|pluralize }}:</p>
+        <ul>
+{% for bundle in bundles %}
+          <li><a href="{{ bundle.get_absolute_url }}">{{ bundle.name }}</a></li>
+{% endfor %}
+        </ul>
+        <p>
+          Visit the <a href="{% url 'user-bundles' %}">bundles page</a> to manage your bundles.
+        </p>
+{% else %}
+        <p>You have no bundles.</p>
+{% endif %}
+      </section>
+
+      <section class="block">
+        <h2 id="todo" class="title is-4">
+          <a href="#todo" title="Permalink to this section">#</a>
+          Todo List
+        </h2>
+        <p>
+          Your <a href="{% url 'user-todos' %}">todo list</a> contains patches that
+          have been delegated to you.
+        </p>
+        <p>
 {% if user.profile.n_todo_patches %}
-      Your have {{ user.profile.n_todo_patches }}
-      patch{{ user.profile.n_todo_patches|pluralize:"es" }} in your todo list.
+          Your have {{ user.profile.n_todo_patches }}
+          patch{{ user.profile.n_todo_patches|pluralize:"es" }} in your todo list.
 {% else %}
-      You have no patches in your todo list at present.
+          You have no patches in your todo list at present.
 {% endif %}
-    </p>
-  </div>
+        </p>
+      </section>
 
-  <div class="box">
-    <h2>Linked email addresses</h2>
-    <p>
-      The following email addresses are associated with this Patchwork account.
-      Adding alternative addresses allows Patchwork to group contributions that
-      you have made under different addresses.
-    </p>
-    <p>
-      The "notify?" column allows you to opt-in or opt-out of automated
-      Patchwork notification emails. Setting it to "no" will disable automated
-      notifications for that address.
-    </p>
-    <p>
-      Adding a new email address will send a confirmation email to that address.
-    </p>
-    <table class="vertical">
-      <tr>
-        <th>email</th>
-        <th>action</th>
-        <th>notify?</th>
-      </tr>
+      <h1 id="settings" class="title">
+        <a href="#settings" title="Permalink to this section"></a>
+        Settings
+      </h1>
+
+{# TODO: Add view to enable this #}
+      <section class="block">
+        <h2 id="profile" class="title is-4">
+          <a href="#profile" title="Permalink to this section">#</a>
+          Profile
+        </h2>
+        <form method="post">
+          {% csrf_token %}
+          <div class="field">
+            <label for="id_username" class="label">
+              Username
+            </label>
+            <div class="control">
+              <input id="id_username" type="text" name="name" class="input" value="{{ user.username }}" disabled>
+            </div>
+          </div>
+          <div class="field">
+            <label for="id_first_name" class="label">
+              First name
+            </label>
+            <div class="control">
+              <input id="id_first_name" type="text" name="first_name" class="input" autocomplete="given-name" value="{{ user.first_name }}">
+            </div>
+          </div>
+          <div class="field">
+            <label for="id_last_name" class="label">
+              Last name
+            </label>
+            <div class="control">
+              <input id="id_last_name" type="text" name="last_name" class="input" autocomplete="family-name" value="{{ user.last_name }}">
+            </div>
+          </div>
+          <div class="control">
+            <button class="button is-primary is-disabled">Save</button>
+          </div>
+        </form>
+      </section>
+
+      <section class="block">
+        <h2 id="linked-emails" class="title is-4">
+          <a href="#linked-emails" title="Permalink to this section">#</a>
+          Linked emails
+        </h2>
 {% for email in linked_emails %}
-      <tr>
-        <td>{{ email.email }}</td>
-        <td>
+        <div class="card">
+          <div class="card-content">
+            <div class="columns">
+              <div class="column">
+                <span>{{ email.email }}</span>
+{% if user.email == email.email %}
+                <span class="tag is-primary is-medium">Primary</span>
+{% endif %}
+              </div>
 {% if user.email != email.email %}
-          <form action="{% url 'user-unlink' person_id=email.id %}" method="post">
-            {% csrf_token %}
-            <input type="submit" value="Unlink"/>
-          </form>
+              <div class="column is-narrow">
+                <form method="post" action="{% url 'user-unlink' person_id=email.id %}">
+                  {% csrf_token %}
+                  <button class="button is-danger">Unlink</button>
+                </form>
+              </div>
+{# TODO: Add view to enable this #}
+              <div class="column is-narrow">
+                <form method="post">
+                  {% csrf_token %}
+                  <button class="button is-info">Make primary</button>
+                </form>
+              </div>
 {% endif %}
-        </td>
-        <td>
+              <div class="column is-narrow">
 {% if email.is_optout %}
-          <form method="post" action="{% url 'mail-optin' %}">
-            {% csrf_token %}
-            No,
-            <input type="hidden" name="email" value="{{ email.email }}"/>
-            <input type="submit" value="Opt-in"/>
-          </form>
+                <form method="post" action="{% url 'mail-optin' %}">
+                  {% csrf_token %}
+                  <input type="hidden" name="email" value="{{ email.email }}"/>
+                  <button class="button is-info is-right">Opt-in</button>
+                </form>
 {% else %}
-          <form method="post" action="{% url 'mail-optout' %}">
-            {% csrf_token %}
-            Yes,
-            <input type="hidden" name="email" value="{{ email.email }}"/>
-            <input type="submit" value="Opt-out"/>
-          </form>
+                <form method="post" action="{% url 'mail-optout' %}">
+                  {% csrf_token %}
+                  <input type="hidden" name="email" value="{{ email.email }}"/>
+                  <button class="button is-info">Opt-out</button>
+                </form>
 {% endif %}
-        </td>
-      </tr>
+              </div>
+            </div>
+          </div>
+        </div>
 {% endfor %}
-      <tr>
-        <td colspan="3">
-          <form action="{% url 'user-link' %}" method="post">
+        <div class="block"></div>
+        <div class="block">
+          <form class="block" method="post" action="{% url 'user-link' %}">
             {% csrf_token %}
-            {{ linkform.email }}
-            <input type="submit" value="Add"/>
+            <label for="id_email" class="label">
+              Add email address
+            </label>
+            <div class="field is-grouped">
+              <div class="control">
+                <input id="id_email" type="email" name="email" placeholder="e.g. bobsmith at example.com" class="input" required>
+              </div>
+              <div class="control">
+                <button class="button is-info">
+                  Add email
+                </button>
+              </div>
+            </div>
           </form>
-        </td>
-      </tr>
-    </table>
-  </div>
-</div>
-
-<div class="rightcol">
-  <div class="box">
-    <h2>Bundles</h2>
-{% if bundles %}
-    <p>You have the following bundle{{ bundles|length|pluralize }}:</p>
-    <ul>
-{% for bundle in bundles %}
-      <li><a href="{{ bundle.get_absolute_url }}">{{ bundle.name }}</a></li>
-{% endfor %}
-    </ul>
-    <p>Visit the <a href="{%url 'user-bundles' %}">bundles page</a> to manage your bundles.</p>
-{% else %}
-    <p>You have no bundles.</p>
-{% endif %}
-  </div>
+        </div>
+      </section>
 
-  <div class="box">
-    <h2>Settings</h2>
+      <section class="block">
+        <h2 id="profile-settings" class="title is-4">
+          <a href="#profile-settings" title="Permalink to this section">#</a>
+          Profile settings
+        </h2>
+        <form class="block" method="post">
+          {% csrf_token %}
+          <div class="field">
+            <label for="id_items_per_page" class="label">
+              Items per page
+            </label>
+            <div class="control">
+              <input id="id_items_per_page" type="number" name="items_per_page" class="input" value="{{ user.profile.items_per_page }}" required>
+              <p class="help">Number of items to display per page</p>
+            </div>
+          </div>
+          <div class="field">
+            <p class="label">
+              Show patch IDs
+            </p>
+            <div class="control">
+              <label class="radio">
+                <input type="radio" name="show_ids">
+                Yes
+              </label>
+              <label class="radio">
+                <input type="radio" name="show_ids">
+                No
+              </label>
+              <p class="help">Show click-to-copy patch IDs in the list view</p>
+            </div>
+          </div>
+          <div class="control">
+            <button class="button is-primary is-disabled">Update settings</button>
+          </div>
+        </form>
+      </section>
 
-    <form method="post">
-      {% csrf_token %}
-      <table class="form">
-        {{ profileform }}
-        <tr>
-          <td></td>
-          <td><input type="submit" value="Apply"/></td>
-        </tr>
-      </table>
-    </form>
-  </div>
-
-  <div class="box">
-    <h2>Authentication</h2>
-
-    <table class="form">
-      <tr>
-        <th>Password:</th>
-        <td><a href="{% url 'password_change' %}">Change password</a>
-      </tr>
-{% if rest_api_enabled %}
-      <tr>
-        <th>API Token:</th>
-        <td>
-{% if api_token %}
-          <input id="token" style="width: 25em;" readonly value="{{ api_token }}">
-          <button type="button" class="btn-copy" title="Copy to clipboard" data-clipboard-target="#token">Copy</button>
-{% endif %}
-        </td>
-      <tr>
-        <th></th>
-        <td>
+      <section class="block">
+        <h2 id="security" class="title is-4">
+          <a href="#security" title="Permalink to this section">#</a>
+          Security
+        </h2>
+        <form class="block" method="post" action="{% url 'password_change' %}">
+          {% csrf_token %}
+          <div class="field">
+            <label for="id_old_password" class="label">
+              Current password
+            </label>
+            <div class="control">
+              <input id="id_old_password" type="password" name="old_password" class="input" required>
+            </div>
+          </div>
+          <div class="field">
+            <label for="id_new_password1" class="label">
+              New password
+            </label>
+            <div class="control">
+              <input id="id_new_password1" type="password" name="new_password1" class="input" required>
+            </div>
+          </div>
+          <div class="field">
+            <label for="id_new_password2" class="label">
+              Confirm password
+            </label>
+            <div class="control">
+              <input id="id_new_password2" type="password" name="new_password2" class="input" required>
+            </div>
+          </div>
+          <div class="control">
+            <button class="button is-primary is-disabled">Update password</button>
+          </div>
+        </form>
+        <div class="block">
+          <label for="id_api_token" class="label">
+            API token
+          </label>
+          <div class="field has-addons">
+            <div class="control is-expanded">
+              <input id="id_api_token" type="text" name="name" class="input" value="{{ api_token|default_if_none:'' }}" disabled>
+            </div>
+{# TODO: wire this up #}
+            <div class="control">
+              <button class="button is-info">
+                Copy
+              </button>
+            </div>
+          </div>
           <form method="post" action="{% url 'generate_token' %}">
             {% csrf_token %}
+            <div class="control">
 {% if api_token %}
-            <input type="submit" value="Regenerate token"/>
+              <button class="button is-primary">Regenerate token</button>
 {% else %}
-            <input type="submit" value="Generate token"/>
+              <button class="button is-primary">Generate token</button>
 {% endif %}
+            </div>
           </form>
-        </td>
-      </tr>
-{% endif %}
-    </table>
+        </div>
+      </section>
+    </div>
   </div>
 </div>
 
-<p style="clear: both"></p>
+<script>
+document.addEventListener('DOMContentLoaded', () => {
+  // Get all "navbar-burger" elements
+  const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0);
+
+  // Check if there are any navbar burgers
+  if ($navbarBurgers.length > 0) {
+    // Add a click event on each of them
+    $navbarBurgers.forEach( el => {
+      el.addEventListener('click', () => {
+        // Get the target from the "data-target" attribute
+        const target = el.dataset.target;
+        const $target = document.getElementById(target);
+
+        // Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
+        el.classList.toggle('is-active');
+        $target.classList.toggle('is-active');
+      });
+    });
+  }
+});
+</script>
 {% endblock %}
diff --git templates/base2.html templates/base2.html
index ac6b43bc..6380c37b 100644
--- templates/base2.html
+++ templates/base2.html
@@ -10,6 +10,88 @@
 {% block headers %}{% endblock %}
   </head>
   <body>
+{% block navigation %}
+    <nav class="navbar is-white" role="navigation" aria-label="main navigation">
+      <div class="container">
+        <div class="navbar-brand">
+          <a class="navbar-item brand-text" href="/">Patchwork</a>
+          <a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navMenu">
+            <span aria-hidden="true"></span>
+            <span aria-hidden="true"></span>
+            <span aria-hidden="true"></span>
+          </a>
+        </div>
+        <div id="navMenu" class="navbar-menu">
+          <div class="navbar-start">
+            <a class="navbar-item" href="{% url 'user-todos' %}">Todo List</a>
+            <a class="navbar-item" href="{% url 'user-bundles' %}">Bundles</a>
+            <a class="navbar-item" href="{% url 'project-list' %}">Projects</a>
+          </div>
+          <div class="navbar-end">
+{% if user.is_authenticated %}
+            <div class="navbar-item has-dropdown is-hoverable">
+              <a class="navbar-link">
+                <span class="icon-text">
+                  <span class="icon">
+                    <i class="fa fa-user"></i>
+                  </span>
+                  <span>{{ user.username }}</span>
+                </span>
+              </a>
+              <div class="navbar-dropdown is-right">
+                <a class="navbar-item" href="{% url 'user-todos' %}">
+                  Todo List
+                </a>
+                <a class="navbar-item" href="{% url 'user-bundles' %}">
+                  Bundles
+                </a>
+                <a class="navbar-item" href="{% url 'user-profile' %}">
+                  Profile
+                </a>
+{% if user.is_staff %}
+                <hr class="navbar-divider">
+                <a class="navbar-item" href="{% url 'admin:index' %}">
+                  Patchwork Settings
+                </a>
+{% endif %}
+                <hr class="navbar-divider">
+                <a class="navbar-item" href="{% url 'auth_logout' %}">
+                  Sign out
+                </a>
+              </div>
+            </div>
+{% else %}
+            <div class="navbar-item">
+              <div class="buttons">
+                <a href="{% url 'user-register' %}" class="button is-primary">
+                  <strong>Sign up</strong>
+                </a>
+                <a href="{% url 'auth_login' %}" class="button is-light">
+                  Log in
+                </a>
+              </div>
+            </div>
+{% endif %}
+          </div>
+        </div>
+      </div>
+    </nav>
+{% endblock %}
 {% block body %}{% endblock %}
+{% block footer %}
+    <div class="block"></div>
+
+    <footer class="footer">
+      <div class="content has-text-centered">
+        <p>
+          <a href="https://github.com/getpatchwork/patchwork/">Patchwork patch tracking system</a>
+           • 
+          Version {{ version }}
+           • 
+          <a href="{% url 'about' %}">About Patchwork</a>
+        </p>
+      </div>
+    </footer>
+{% endblock %}
   </body>
 </html>
diff --git templates/registration/password_reset_confirm.html templates/registration/password_reset_confirm.html
index 1c91eb1b..90a7e136 100644
--- templates/registration/password_reset_confirm.html
+++ templates/registration/password_reset_confirm.html
@@ -3,6 +3,8 @@
 {% block title %}Password reset confirmation{% endblock %}
 {% block heading %}Password reset confirmation{% endblock %}
 
+{% block navigation %}{% endblock %}
+
 {% block body %}
 <section class="hero is-primary is-fullheight">
   <div class="hero-body">
@@ -75,3 +77,5 @@
   </div>
 </section>
 {% endblock %}
+
+{% block footer %}{% endblock %}
diff --git templates/registration/password_reset_done.html templates/registration/password_reset_done.html
index ebd38e68..65b65e77 100644
--- templates/registration/password_reset_done.html
+++ templates/registration/password_reset_done.html
@@ -2,6 +2,8 @@
 
 {% block title %}Password reset email sent!{% endblock %}
 
+{% block navigation %}{% endblock %}
+
 {% block body %}
 <section class="hero is-primary is-fullheight">
   <div class="hero-body">
@@ -26,3 +28,5 @@
   </div>
 </section>
 {% endblock %}
+
+{% block footer %}{% endblock %}
diff --git templates/registration/password_reset_form.html templates/registration/password_reset_form.html
index 431b4696..4f0c3a6a 100644
--- templates/registration/password_reset_form.html
+++ templates/registration/password_reset_form.html
@@ -2,6 +2,8 @@
 
 {% block title %}Forgot your password?{% endblock %}
 
+{% block navigation %}{% endblock %}
+
 {% block body %}
 <section class="hero is-primary is-fullheight">
   <div class="hero-body">
@@ -46,3 +48,5 @@
   </div>
 </section>
 {% endblock %}
+
+{% block footer %}{% endblock %}
-- 
2.31.1



More information about the Patchwork mailing list