[PATCH v3 01/12] views: Integrate cover letter support
Stephen Finucane
stephen.finucane at intel.com
Tue Apr 12 07:09:16 AEST 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>
Reviewed-by: Andy Doan <andy.doan at linaro.org>
---
v2: Remove 'PatchworkRequestContext' per changes in upstream
---
patchwork/templates/patchwork/patch.html | 239 -------------------------
patchwork/templates/patchwork/submission.html | 244 ++++++++++++++++++++++++++
patchwork/templatetags/syntax.py | 4 +-
patchwork/tests/test_detail.py | 54 ++++++
patchwork/tests/utils.py | 23 ++-
patchwork/urls.py | 5 +
patchwork/views/cover.py | 50 ++++++
patchwork/views/patch.py | 26 ++-
8 files changed, 397 insertions(+), 248 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 8b6d1fe..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>
-
-{% 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 %}
-
-{% 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 %}
-
-{% endblock %}
diff --git a/patchwork/templates/patchwork/submission.html b/patchwork/templates/patchwork/submission.html
new file mode 100644
index 0000000..bda36f9
--- /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>
+
+{% 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 %}
+
+{% 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 %}
+
+{% endblock %}
diff --git a/patchwork/templatetags/syntax.py b/patchwork/templatetags/syntax.py
index 85ed201..6cb8ff8 100644
--- a/patchwork/templatetags/syntax.py
+++ b/patchwork/templatetags/syntax.py
@@ -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 f3fdc5b..f664501 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..83f5551
--- /dev/null
+++ b/patchwork/views/cover.py
@@ -0,0 +1,50 @@
+# 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
+
+
+def cover(request, cover_id):
+ context = {}
+
+ # 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,
+ 'project': cover.project,
+ }
+
+ return render_to_response('patchwork/submission.html', context)
diff --git a/patchwork/views/patch.py b/patchwork/views/patch.py
index 002d700..3346568 100644
--- a/patchwork/views/patch.py
+++ b/patchwork/views/patch.py
@@ -20,19 +20,33 @@
from __future__ import absolute_import
from django.contrib import messages
-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, 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.views import generic_list, patch_to_mbox
def patch(request, patch_id):
- patch = get_object_or_404(Patch, id=patch_id)
- 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)
context = {
'project': patch.project
}
@@ -86,12 +100,12 @@ def patch(request, patch_id):
if request.user.is_authenticated():
context['bundles'] = Bundle.objects.filter(owner=request.user)
- context['patch'] = patch
+ context['submission'] = patch
context['patchform'] = form
context['createbundleform'] = createbundleform
context['project'] = patch.project
- return render(request, 'patchwork/patch.html', context)
+ return render(request, 'patchwork/submission.html', context)
def content(request, patch_id):
--
2.0.0
More information about the Patchwork
mailing list