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 <[email protected]> --- 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 _______________________________________________ Patchwork mailing list [email protected] https://lists.ozlabs.org/listinfo/patchwork
