[PATCH 04/13] views: Integrate cover letter support

Stephen Finucane stephen.finucane at intel.com
Fri Mar 11 23:08:08 AEDT 2016


There isn't really any need to list cover letters right now, seeing as
they're really only valuable in the context of series. However, if
someone requests a cover letter by ID then this should be displayed.
To this effect, add a new "covers" endpoint that can display the basic
elements of the cover letter. This includes redirects from/to the
"patches" endpoint.

Signed-off-by: Stephen Finucane <stephen.finucane at intel.com>
---
 patchwork/templates/patchwork/patch.html      | 239 -------------------------
 patchwork/templates/patchwork/submission.html | 244 ++++++++++++++++++++++++++
 patchwork/templatetags/syntax.py              |   6 +-
 patchwork/tests/test_detail.py                |  54 ++++++
 patchwork/tests/utils.py                      |  23 ++-
 patchwork/urls.py                             |   5 +
 patchwork/views/cover.py                      |  49 ++++++
 patchwork/views/patch.py                      |  28 ++-
 8 files changed, 398 insertions(+), 250 deletions(-)
 delete mode 100644 patchwork/templates/patchwork/patch.html
 create mode 100644 patchwork/templates/patchwork/submission.html
 create mode 100644 patchwork/tests/test_detail.py
 create mode 100644 patchwork/views/cover.py

diff --git a/patchwork/templates/patchwork/patch.html b/patchwork/templates/patchwork/patch.html
deleted file mode 100644
index a05f00d..0000000
--- a/patchwork/templates/patchwork/patch.html
+++ /dev/null
@@ -1,239 +0,0 @@
-{% extends "base.html" %}
-
-{% load humanize %}
-{% load syntax %}
-{% load person %}
-{% load patch %}
-
-{% block title %}{{patch.name}}{% endblock %}
-
-{% block body %}
-<script type="text/javascript">
-function toggle_headers(link_id, headers_id)
-{
-    var link = document.getElementById(link_id)
-    var headers = document.getElementById(headers_id)
-
-    var hidden = headers.style['display'] == 'none';
-
-    if (hidden) {
-        link.innerHTML = 'hide';
-        headers.style['display'] = 'block';
-    } else {
-        link.innerHTML = 'show';
-        headers.style['display'] = 'none';
-    }
-
-}
-</script>
-
-<h1>{{ patch.name }}</h1>
-<div class="core-info">
-    <span>Submitted by {{ patch.submitter|personify:project }} on {{ patch.date }}</span>
-</div>
-
-<h2>Details</h2>
-
-<table class="patchmeta">
- <tr>
-  <th>Message ID</th>
-  <td>{{ patch.msgid|msgid }}</td>
- </tr>
-  <tr>
-   <th>State</th>
-   <td>{{ patch.state.name }}{% if patch.archived %}, archived{% endif %}</td>
-  </tr>
-{% if patch.commit_ref %}
-  <tr>
-   <th>Commit</th>
-   <td>{{ patch.commit_ref }}</td>
-  </tr>
-{% endif %}
-{% if patch.delegate %}
-  <tr>
-   <th>Delegated to:</th>
-   <td>{{ patch.delegate.profile.name }}</td>
-  </tr>
-{% endif %}
- <tr>
-  <th>Headers</th>
-  <td><a id="togglepatchheaders"
-   href="javascript:toggle_headers('togglepatchheaders', 'patchheaders')"
-   >show</a>
-   <div id="patchheaders" class="patchheaders" style="display:none;">
-    <pre>{{patch.headers}}</pre>
-   </div>
-  </td>
- </tr>
-</table>
-
-<div class="patchforms">
-
-{% if patchform %}
- <div class="patchform patchform-properties">
-  <h3>Patch Properties</h3>
-   <form method="post">
-    {% csrf_token %}
-    <table class="form">
-     <tr>
-      <th>Change state:</th>
-      <td>
-       {{ patchform.state }}
-       {{ patchform.state.errors }}
-      </td>
-     </tr>
-     <tr>
-      <th>Delegate to:</th>
-      <td>
-       {{ patchform.delegate }}
-       {{ patchform.delegate.errors }}
-      </td>
-     </tr>
-     <tr>
-      <th>Archived:</th>
-      <td>
-       {{ patchform.archived }}
-       {{ patchform.archived.errors }}
-      </td>
-     </tr>
-     <tr>
-      <td></td>
-      <td>
-       <input type="submit" value="Update">
-      </td>
-     </tr>
-    </table>
-  </form>
- </div>
-{% endif %}
-
-{% if createbundleform %}
- <div class="patchform patchform-bundle">
-  <h3>Bundling</h3>
-   <table class="form">
-    <tr>
-     <td>Create bundle:</td>
-     <td>
-       {% if createbundleform.non_field_errors %}
-       <dd class="errors">{{createbundleform.non_field_errors}}</dd>
-       {% endif %}
-      <form method="post">
-       {% csrf_token %}
-       <input type="hidden" name="action" value="createbundle"/>
-       {% if createbundleform.name.errors %}
-       <dd class="errors">{{createbundleform.name.errors}}</dd>
-       {% endif %}
-        {{ createbundleform.name }}
-       <input value="Create" type="submit"/>
-      </form>
-      </td>
-    </tr>
-{% if bundles %}
-    <tr>
-     <td>Add to bundle:</td>
-     <td>
-      <form method="post">
-       {% csrf_token %}
-       <input type="hidden" name="action" value="addtobundle"/>
-       <select name="bundle_id"/>
-        {% for bundle in bundles %}
-         <option value="{{bundle.id}}">{{bundle.name}}</option>
-        {% endfor %}
-        </select>
-       <input value="Add" type="submit"/>
-      </form>
-     </td>
-    </tr>
-{% endif %}
-   </table>
-
- </div>
-{% endif %}
-
- <div style="clear: both;">
- </div>
-</div>
-
-{% if patch.pull_url %}
-<h2>Pull-request</h2>
-<a class="patch-pull-url" href="{{patch.pull_url}}"
- >{{ patch.pull_url }}</a>
-{% endif %}
-
-{% if patch.checks %}
-<h2>Checks</h2>
-<table class="checks">
-<tr>
-  <th>Context</th>
-  <th>Check</th>
-  <th>Description</th>
-</tr>
-{% for check in patch.checks %}
-<tr>
-  <td>{{ check.context }}</td>
-  <td>
-    <span title="Updated {{ check.date|naturaltime }}"
-        class="state {{ check.get_state_display }}">
-      {{ check.get_state_display }}
-    </span>
-  </td>
-  <td>
-    {% if check.target_url %}
-    <a href="{{ check.target_url }}">
-    {% endif %}
-      {{ check.description }}
-    {% if check.target_url %}
-    </a>
-    {% endif %}
-  </td>
-</tr>
-{% endfor %}
-</table>
-{% endif %}
-
-<h2>Commit Message</h2>
-<div class="comment">
-<div class="meta">
- <span>{{ patch.submitter|personify:project }}</span>
- <span class="pull-right">{{ patch.date }}</span>
-</div>
-<pre class="content">
-{{ patch|commentsyntax }}
-</pre>
-</div>
-
-{% if patch.diff %}
-<h2>
- Patch
- <a href="javascript:toggle_headers('hide-patch', 'patch')" id="hide-patch">hide</a></span>
- <span>|</span>
- <a href="{% url 'patch-raw' patch_id=patch.id %}"
-   >download patch</a>
- <span>|</span>
- <a href="{% url 'patch-mbox' patch_id=patch.id %}"
-   >download mbox</a>
-</h2>
-<div id="patch" class="patch">
-<pre class="content">
-{{ patch|patchsyntax }}
-</pre>
-</div>
-{% endif %}
-
-{% for item in patch.comments.all %}
-{% if forloop.first %}
-<h2>Comments</h2>
-{% endif %}
-
-<div class="comment">
-<div class="meta">
- <span>{{ item.submitter|personify:project }}</span>
- <span class="pull-right">{{ item.date }}</span>
-</div>
-<pre class="content">
-{{ item|commentsyntax }}
-</pre>
-</div>
-{% endfor %}
-
-{% endblock %}
diff --git a/patchwork/templates/patchwork/submission.html b/patchwork/templates/patchwork/submission.html
new file mode 100644
index 0000000..7460c0b
--- /dev/null
+++ b/patchwork/templates/patchwork/submission.html
@@ -0,0 +1,244 @@
+{% extends "base.html" %}
+
+{% load humanize %}
+{% load syntax %}
+{% load person %}
+{% load patch %}
+
+{% block title %}{{submission.name}}{% endblock %}
+
+{% block body %}
+<script type="text/javascript">
+function toggle_headers(link_id, headers_id)
+{
+    var link = document.getElementById(link_id)
+    var headers = document.getElementById(headers_id)
+
+    var hidden = headers.style['display'] == 'none';
+
+    if (hidden) {
+        link.innerHTML = 'hide';
+        headers.style['display'] = 'block';
+    } else {
+        link.innerHTML = 'show';
+        headers.style['display'] = 'none';
+    }
+
+}
+</script>
+
+<h1>{{ submission.name }}</h1>
+<div class="core-info">
+ <span>Submitted by {{ submission.submitter|personify:project }} on {{ submission.date }}</span>
+</div>
+
+<h2>Details</h2>
+
+<table class="patchmeta">
+ <tr>
+  <th>Message ID</th>
+  <td>{{ submission.msgid|msgid }}</td>
+ </tr>
+{% if submission.state %}
+ <tr>
+  <th>State</th>
+  <td>{{ submission.state.name }}{% if submission.archived %}, archived{% endif %}</td>
+ </tr>
+{% endif %}
+{% if submission.commit_ref %}
+ <tr>
+  <th>Commit</th>
+  <td>{{ submission.commit_ref }}</td>
+ </tr>
+{% endif %}
+{% if submission.delegate %}
+ <tr>
+  <th>Delegated to:</th>
+  <td>{{ submission.delegate.profile.name }}</td>
+ </tr>
+{% endif %}
+ <tr>
+  <th>Headers</th>
+  <td><a id="togglepatchheaders"
+   href="javascript:toggle_headers('togglepatchheaders', 'patchheaders')"
+   >show</a>
+   <div id="patchheaders" class="patchheaders" style="display:none;">
+    <pre>{{submission.headers}}</pre>
+   </div>
+  </td>
+ </tr>
+</table>
+
+<div class="patchforms">
+{% if patchform %}
+ <div class="patchform patchform-properties">
+  <h3>Patch Properties</h3>
+   <form method="post">
+    {% csrf_token %}
+    <table class="form">
+     <tr>
+      <th>Change state:</th>
+      <td>
+       {{ patchform.state }}
+       {{ patchform.state.errors }}
+      </td>
+     </tr>
+     <tr>
+      <th>Delegate to:</th>
+      <td>
+       {{ patchform.delegate }}
+       {{ patchform.delegate.errors }}
+      </td>
+     </tr>
+     <tr>
+      <th>Archived:</th>
+      <td>
+       {{ patchform.archived }}
+       {{ patchform.archived.errors }}
+      </td>
+     </tr>
+     <tr>
+      <td></td>
+      <td>
+       <input type="submit" value="Update">
+      </td>
+     </tr>
+    </table>
+  </form>
+ </div>
+{% endif %}
+
+{% if createbundleform %}
+ <div class="patchform patchform-bundle">
+  <h3>Bundling</h3>
+   <table class="form">
+    <tr>
+     <td>Create bundle:</td>
+     <td>
+       {% if createbundleform.non_field_errors %}
+       <dd class="errors">{{createbundleform.non_field_errors}}</dd>
+       {% endif %}
+      <form method="post">
+       {% csrf_token %}
+       <input type="hidden" name="action" value="createbundle"/>
+       {% if createbundleform.name.errors %}
+       <dd class="errors">{{createbundleform.name.errors}}</dd>
+       {% endif %}
+        {{ createbundleform.name }}
+       <input value="Create" type="submit"/>
+      </form>
+      </td>
+    </tr>
+{% if bundles %}
+    <tr>
+     <td>Add to bundle:</td>
+     <td>
+      <form method="post">
+       {% csrf_token %}
+       <input type="hidden" name="action" value="addtobundle"/>
+       <select name="bundle_id"/>
+        {% for bundle in bundles %}
+         <option value="{{bundle.id}}">{{bundle.name}}</option>
+        {% endfor %}
+        </select>
+       <input value="Add" type="submit"/>
+      </form>
+     </td>
+    </tr>
+{% endif %}
+   </table>
+
+ </div>
+{% endif %}
+
+ <div style="clear: both;">
+ </div>
+</div>
+
+{% if submission.pull_url %}
+<h2>Pull-request</h2>
+<a class="patch-pull-url" href="{{submission.pull_url}}"
+ >{{ submission.pull_url }}</a>
+{% endif %}
+
+{% if submission.checks %}
+<h2>Checks</h2>
+<table class="checks">
+<tr>
+  <th>Context</th>
+  <th>Check</th>
+  <th>Description</th>
+</tr>
+{% for check in submission.checks %}
+<tr>
+  <td>{{ check.context }}</td>
+  <td>
+    <span title="Updated {{ check.date|naturaltime }}"
+        class="state {{ check.get_state_display }}">
+      {{ check.get_state_display }}
+    </span>
+  </td>
+  <td>
+    {% if check.target_url %}
+    <a href="{{ check.target_url }}">
+    {% endif %}
+      {{ check.description }}
+    {% if check.target_url %}
+    </a>
+    {% endif %}
+  </td>
+</tr>
+{% endfor %}
+</table>
+{% endif %}
+
+{% if submission.diff %}
+<h2>Commit Message</h2>
+{% else %}
+<h2>Message</h2>
+{% endif %}
+<div class="comment">
+<div class="meta">
+ <span>{{ submission.submitter|personify:project }}</span>
+ <span class="pull-right">{{ submission.date }}</span>
+</div>
+<pre class="content">
+{{ submission|commentsyntax }}
+</pre>
+</div>
+
+{% if submission.diff %}
+<h2>
+ Patch
+ <a href="javascript:toggle_headers('hide-patch', 'patch')" id="hide-patch">hide</a></span>
+ <span>|</span>
+ <a href="{% url 'patch-raw' patch_id=submission.id %}"
+   >download patch</a>
+ <span>|</span>
+ <a href="{% url 'patch-mbox' patch_id=submission.id %}"
+   >download mbox</a>
+</h2>
+<div id="patch" class="patch">
+<pre class="content">
+{{ submission|patchsyntax }}
+</pre>
+</div>
+{% endif %}
+
+{% for item in submission.comments.all %}
+{% if forloop.first %}
+<h2>Comments</h2>
+{% endif %}
+
+<div class="comment">
+<div class="meta">
+ <span>{{ item.submitter|personify:project }}</span>
+ <span class="pull-right">{{ item.date }}</span>
+</div>
+<pre class="content">
+{{ item|commentsyntax }}
+</pre>
+</div>
+{% endfor %}
+
+{% endblock %}
diff --git a/patchwork/templatetags/syntax.py b/patchwork/templatetags/syntax.py
index 1e4d292..6cb8ff8 100644
--- a/patchwork/templatetags/syntax.py
+++ b/patchwork/templatetags/syntax.py
@@ -59,7 +59,7 @@ _span = '<span class="%s">%s</span>'
 
 @register.filter
 def patchsyntax(patch):
-    diff = escape(patch.content).replace('\r\n', '\n')
+    diff = escape(patch.diff).replace('\r\n', '\n')
 
     for (r, cls) in _patch_span_res:
         diff = r.sub(lambda x: _span % (cls, x.group(0)), diff)
@@ -74,8 +74,8 @@ def patchsyntax(patch):
 
 
 @register.filter
-def commentsyntax(patch):
-    content = escape(patch.content)
+def commentsyntax(submission):
+    content = escape(submission.content)
 
     for (r, cls) in _comment_span_res:
         content = r.sub(lambda x: _span % (cls, x.group(0)), content)
diff --git a/patchwork/tests/test_detail.py b/patchwork/tests/test_detail.py
new file mode 100644
index 0000000..bf6df96
--- /dev/null
+++ b/patchwork/tests/test_detail.py
@@ -0,0 +1,54 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2016 Intel Corporation
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from __future__ import absolute_import
+
+from django.core.urlresolvers import reverse
+from django.test import TestCase
+
+from patchwork.tests.utils import create_covers
+from patchwork.tests.utils import create_patches
+
+
+class CoverLetterViewTest(TestCase):
+    fixtures = ['default_states']
+
+    def testRedirect(self):
+        patches = create_patches()
+        patch_id = patches[0].id
+
+        requested_url = reverse('cover-detail', kwargs={'cover_id': patch_id})
+        redirect_url = reverse('patch-detail', kwargs={'patch_id': patch_id})
+
+        response = self.client.post(requested_url)
+        self.assertRedirects(response, redirect_url)
+
+
+class PatchViewTest(TestCase):
+    fixtures = ['default_states']
+
+    def testRedirect(self):
+        covers = create_covers()
+        cover_id = covers[0].id
+
+        requested_url = reverse('patch-detail', kwargs={'patch_id': cover_id})
+        redirect_url = reverse('cover-detail', kwargs={'cover_id': cover_id})
+
+        response = self.client.post(requested_url)
+        self.assertRedirects(response, redirect_url)
diff --git a/patchwork/tests/utils.py b/patchwork/tests/utils.py
index 375f188..2608782 100644
--- a/patchwork/tests/utils.py
+++ b/patchwork/tests/utils.py
@@ -26,7 +26,10 @@ import os
 
 from django.contrib.auth.models import User
 
-from patchwork.models import Project, Person, Patch
+from patchwork.models import CoverLetter
+from patchwork.models import Patch
+from patchwork.models import Person
+from patchwork.models import Project
 
 
 # helper functions for tests
@@ -105,6 +108,24 @@ def create_patches(count=1):
     return patches
 
 
+def create_covers(count=1):
+    """Create 'count' unique cover letters."""
+    defaults.project.save()
+    defaults.patch_author_person.save()
+
+    covers = []
+
+    for i in range(0, count):
+        cover = CoverLetter(project=defaults.project,
+                            submitter=defaults.patch_author_person,
+                            msgid=make_msgid(),
+                            name='testcover%d' % (i + 1))
+        cover.save()
+        covers.append(cover)
+
+    return covers
+
+
 def find_in_context(context, key):
     if isinstance(context, list):
         for c in context:
diff --git a/patchwork/urls.py b/patchwork/urls.py
index 022b92c..0c1b87c 100644
--- a/patchwork/urls.py
+++ b/patchwork/urls.py
@@ -25,6 +25,7 @@ from django.contrib.auth import views as auth_views
 from patchwork import views
 from patchwork.views import api as api_views
 from patchwork.views import bundle as bundle_views
+from patchwork.views import cover as cover_views
 from patchwork.views import help as help_views
 from patchwork.views import mail as mail_views
 from patchwork.views import patch as patch_views
@@ -55,6 +56,10 @@ urlpatterns = [
     url(r'^patch/(?P<patch_id>\d+)/mbox/$', patch_views.mbox,
         name='patch-mbox'),
 
+    # cover views
+    url(r'^cover/(?P<cover_id>\d+)/$', cover_views.cover,
+        name='cover-detail'),
+
 
     # logged-in user stuff
     url(r'^user/$', user_views.profile, name='user-profile'),
diff --git a/patchwork/views/cover.py b/patchwork/views/cover.py
new file mode 100644
index 0000000..5de6ac7
--- /dev/null
+++ b/patchwork/views/cover.py
@@ -0,0 +1,49 @@
+# Patchwork - automated patch tracking system
+# Copyright (C) 2016 Intel Corporation
+#
+# This file is part of the Patchwork package.
+#
+# Patchwork is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# Patchwork is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Patchwork; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+from __future__ import absolute_import
+
+from django.core import urlresolvers
+from django.http import Http404
+from django.http import HttpResponseRedirect
+from django.shortcuts import render_to_response, get_object_or_404
+
+from patchwork.models import CoverLetter, Submission
+from patchwork.requestcontext import PatchworkRequestContext
+
+
+def cover(request, cover_id):
+    context = PatchworkRequestContext(request)
+
+    # redirect to patches where necessary
+    try:
+        cover = get_object_or_404(CoverLetter, id=cover_id)
+    except Http404 as exc:
+        submissions = Submission.objects.filter(id=cover_id)
+        if submissions:
+            return HttpResponseRedirect(
+                urlresolvers.reverse(
+                    'patch-detail',
+                    kwargs={'patch_id': cover_id}))
+        raise exc
+
+    context['submission'] = cover
+    context['project'] = cover.project
+
+    return render_to_response('patchwork/submission.html', context)
diff --git a/patchwork/views/patch.py b/patchwork/views/patch.py
index aa0e9cc..c911201 100644
--- a/patchwork/views/patch.py
+++ b/patchwork/views/patch.py
@@ -19,22 +19,36 @@
 
 from __future__ import absolute_import
 
-from django.http import HttpResponse, HttpResponseForbidden
+from django.core import urlresolvers
+from django.http import Http404
+from django.http import HttpResponse
+from django.http import HttpResponseForbidden
+from django.http import HttpResponseRedirect
 from django.shortcuts import render_to_response, get_object_or_404
 from django.utils import six
 
 from patchwork.forms import PatchForm, CreateBundleForm
-from patchwork.models import Patch, Project, Bundle
+from patchwork.models import Patch, Project, Bundle, Submission
 from patchwork.requestcontext import PatchworkRequestContext
 from patchwork.views import generic_list, patch_to_mbox
 
 
 def patch(request, patch_id):
     context = PatchworkRequestContext(request)
-    patch = get_object_or_404(Patch, id=patch_id)
-    context.project = patch.project
-    editable = patch.is_editable(request.user)
 
+    # redirect to cover letters where necessary
+    try:
+        patch = get_object_or_404(Patch, id=patch_id)
+    except Http404 as exc:
+        submissions = Submission.objects.filter(id=patch_id)
+        if submissions:
+            return HttpResponseRedirect(
+                urlresolvers.reverse(
+                    'cover-detail',
+                    kwargs={'cover_id': patch_id}))
+        raise exc
+
+    editable = patch.is_editable(request.user)
     form = None
     createbundleform = None
 
@@ -80,12 +94,12 @@ def patch(request, patch_id):
                 form.save()
                 context.add_message('Patch updated')
 
-    context['patch'] = patch
+    context['submission'] = patch
     context['patchform'] = form
     context['createbundleform'] = createbundleform
     context['project'] = patch.project
 
-    return render_to_response('patchwork/patch.html', context)
+    return render_to_response('patchwork/submission.html', context)
 
 
 def content(request, patch_id):
-- 
2.0.0



More information about the Patchwork mailing list