New motion version diff view. Improved version history table in motion detail template.

This commit is contained in:
Emanuel Schuetze 2013-03-14 21:28:03 +01:00
parent 196ad217b0
commit a1a11ff53c
6 changed files with 207 additions and 78 deletions

View File

@ -15,6 +15,8 @@
from datetime import datetime
import difflib
from django.core.urlresolvers import reverse
from django.db import models
from django.db.models import Max
@ -496,6 +498,12 @@ class MotionVersion(models.Model):
"""Return True, if the version is the active version of a motion. Else: False."""
return self.active_version.exists()
def make_htmldiff(self, rev1, rev2):
"""Return string of html diff between two strings (rev1 and rev2)"""
diff = difflib.HtmlDiff(wrapcolumn=60)
return diff.make_table(rev1.splitlines(), rev2.splitlines())
class MotionSubmitter(models.Model):
"""Save the submitter of a Motion."""

View File

@ -0,0 +1,41 @@
/**
* OpenSlides motion style
*
* :copyright: 2013 by OpenSlides team, see AUTHORS.
* :license: GNU GPL, see LICENSE for more details.
*/
/* motion version diff table */
table.diff, .diff_row {
font-size: 12px;
width: 100%;
border: 0px;
}
table.diff[rules][rules="groups"] > colgroup, table[rules][rules="groups"] > tfoot, table[rules][rules="groups"] > thead, table[rules][rules="groups"] > tbody {
border: none;
}
table.diff td {
padding: 0 !important;
border: none;
}
.diff_header {
background-color:#e0e0e0;
}
td.diff_header {
text-align:right;
display: none;
}
.diff_next {
background-color:#c0c0c0;
display: none;
}
.diff_add {
background-color:#aaffaa;
}
.diff_chg {
background-color:#ffff77;
}
.diff_sub {
background-color:#ffaaaa;
}

View File

@ -56,14 +56,14 @@
<div class="row-fluid">
<div class="span8">
{% if motion.active_version.id != motion.version.id %}
<span class="label label-warning"><i class="icon-warning-sign icon-white"></i>
{% if motion.version.version_number < motion.last_version.version_number %}
{% trans "This is not the newest version." %}</span> <a href="{% model_url motion.last_version %}">{% trans "Go to version" %} {{ motion.last_version.version_number }}.</a>
{% else %}
{% trans "This is not the authorized version." %}</span> <a href="{% model_url motion.active_version %}">{% trans "Go to version" %} {{ motion.active_version.version_number }}.</a>
{% endif %}
{% endif %}
<!-- Text -->
<h4>{% trans "Motion text" %}:</h4>
@ -80,84 +80,60 @@
<br>
<!-- Version history -->
{% for version in motion.versions.all %}
{% if forloop.first %}
<h4>{% trans "Version history" %}:</h4>
<table class="table table-striped table-bordered">
<tr>
<th></th>
<th>{% trans "Version" %}</th>
<th>{% trans "Time" %}</th>
<th>{% trans "Title" %}</th>
<th>{% trans "Text" %}</th>
<th>{% trans "Reason" %}</th>
</tr>
{% endif %}
<tr>
<td class="nobr">
{% if version == motion.active_version %}
<span class="badge badge-success" title="{% trans 'This version is authorized' %}"><i class="icon-ok icon-white"></i></span>
{% else %}
{% if perms.motion.can_manage_motion %}
<a class="btn btn-mini" href="{% url 'motion_version_permit' motion.id version.version_number %}" title="{% trans 'Permit this version' %}"><i class="icon-ok"></i></a>
{% endif %}
{% if not version.rejected and version.id > motion.active_version.id and perms.motion.can_manage_motion %}
<a class="btn btn-mini" href="{% url 'motion_version_reject' motion.id version.version_number %}" title="{% trans 'Reject this version' %}"><i class="icon-ban-circle"></i></a>
{% endif %}
{% with versions=motion.versions.all %}
{% if versions|length > 1 %}
{% for version in versions %}
{% if forloop.first %}
<h4>{% trans "Version history" %}:</h4>
<form action="{% url 'motion_version_diff' motion.pk %}" method="get">
<table class="table table-striped table-bordered">
<tr>
<th></th>
<th>#</th>
<th>{% trans "Time" %}</th>
<th><button class="btn btn-small" type="submit">{% trans 'Difference' %}</button></th>
<th>{% trans "Actions" %}</th>
</tr>
{% endif %}
{% if version.rejected %}
<span class="badge badge-important" title="{% trans 'This version is rejected' %}"><i class="icon-ban-circle icon-white"></i></span>
{% endif %}
</td>
<td><a href="{% model_url version %}">{{ version.version_number }}</a></td>
<td><i>{{ version.creation_time }}</i></td>
<td>
{% ifchanged %}
<b>{{ version.title }}</b>
{% else %}
<i>[{% trans "unchanged" %}]</i>
{% endifchanged %}
</td>
<td>
{% ifchanged %}
{{ version.text|linebreaks }}
{% else %}
<i>[{% trans "unchanged" %}]</i>
{% endifchanged %}
</td>
<td>
{% ifchanged %}
{{ version.reason|linebreaks }}
{% else %}
<i>[{% trans "unchanged" %}]</i>
{% endifchanged %}
</td>
</tr>
{% if forloop.last %}
</table>
<tr {% if version == motion.version %}class="offline"{%endif %}>
<td class="nobr">
{% if version == motion.active_version %}
<span class="badge badge-success" title="{% trans 'This version is authorized' %}"><i class="icon-ok icon-white"></i></span>
{% else %}
{% if perms.motion.can_manage_motion %}
<a class="btn btn-mini" href="{% url 'motion_version_permit' motion.id version.version_number %}" title="{% trans 'Permit this version' %}"><i class="icon-ok"></i></a>
{% endif %}
{% if not version.rejected and version.id > motion.active_version.id and perms.motion.can_manage_motion %}
<a class="btn btn-mini" href="{% url 'motion_version_reject' motion.id version.version_number %}" title="{% trans 'Reject this version' %}"><i class="icon-ban-circle"></i></a>
{% endif %}
{% endif %}
{% if version.rejected %}
<span class="badge badge-important" title="{% trans 'This version is rejected' %}"><i class="icon-ban-circle icon-white"></i></span>
{% endif %}
</td>
<td>{{ version.version_number }}</td>
<td><i>{{ version.creation_time }}</i></td>
<td>
<input type="radio" value="{{ version.version_number }}" name="rev1">
<input type="radio" value="{{ version.version_number }}" name="rev2">
</td>
<td>
<a href="{% model_url version %}" title="{% trans 'Show version number' %} {{ version.version_number }}" class="btn btn-mini">
<i class="icon-search"></i>
</a>
{# TODO: add delete version function #}
<a href="{% model_url version 'delete' %}" title="{% trans 'Delete version number' %} {{ version.version_number }}" class="btn btn-mini">
<i class="icon-remove"></i>
</a>
</td>
</tr>
{% if forloop.last %}
</table>
</form>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
<!-- TODO: For testing -->
<ol>
{% for motion_version in motion.versions.all %}
<li>
{% if motion_version.id == motion.version.id %}
<strong><a href="{% model_url motion_version %}">{{ motion_version }}</a></strong>
{% else %}
<a href="{% model_url motion_version %}">{{ motion_version }}</a>
{% endif %}
{% if motion_version.active %}
(active)
{% endif %}
{% if motion_version.rejected %}
(rejected)
{% endif %}
</li>
{% endfor %}
</ol>
<!-- End TODO -->
{% endwith %}
<!-- Log -->
{% if perms.motion.can_manage_motion %}

View File

@ -0,0 +1,67 @@
{% extends "base.html" %}
{% load tags %}
{% load i18n %}
{% load staticfiles %}
{% block title %}{{ block.super }} {% trans "Motion" %} {{ motion.number }}{% endblock %}
{% block header %}
<link type="text/css" rel="stylesheet" media="all" href="{% static 'styles/motion.css' %}" />
{% endblock %}
{% block content %}
<h1>
{{ motion.title }}
<br>
<small>
{% if motion.number != None %}
{% trans "Motion" %} {{ motion.number }},
{% else %}
<i>[{% trans "no number" %}]</i>,
{% endif %}
{% trans "Diff view" %}
</small>
<small class="pull-right">
<div class="btn-toolbar">
<a href="{% url 'motion_detail' motion.id %}" class="btn btn-mini"><i class="icon-chevron-left"></i> {% trans "Back to motion" %}</a>
</div>
</small>
</h1>
{% if version_rev1 and version_rev2 %}
<table class="table table-striped table-bordered">
<tr>
<td><b>{% trans "Version" %} {{ version_rev1.version_number }}</b><br>
{% trans "created: " %} {{ version_rev1.creation_time }}<br>
<h4>{{ version_rev1.title }}</h4>
</td>
<td><b>{% trans "Version" %} {{ version_rev2.version_number }}</b><br>
{% trans "created: " %} {{ version_rev1.creation_time }}<br>
<h4>{{ version_rev2.title }}</h4>
</td>
</tr>
<!-- Text -->
<tr class="diff_row">
{% if not version_rev1.text == version_rev2.text %}
<td colspan="2">{{ diff_text|safe }}</td>
{% else %}
<td>{{ version_rev1.text }}</td>
<td>{{ version_rev2.text }}</td>
{% endif %}
</tr>
<!-- Reason -->
<tr><td colspan="2"><h4>{% trans "Reason" %}:</h4></td></tr>
<tr class="diff_row">
{% if not version_rev1.reason == version_rev2.reason %}
<td colspan="2">{{ diff_reason|safe }}</td>
{% else %}
<td>{{ version_rev1.reason }}</td>
<td>{{ version_rev2.reason }}</td>
{% endif %}
</tr>
</table>
{% endif %}
{% endblock %}

View File

@ -55,6 +55,11 @@ urlpatterns = patterns('openslides.motion.views',
name='motion_version_reject',
),
url(r'^(?P<pk>\d+)/diff/$',
'version_diff',
name='motion_version_diff',
),
url(r'^(?P<pk>\d+)/support/$',
'motion_support',
name='motion_support',

View File

@ -230,6 +230,38 @@ class VersionRejectView(GetVersionMixin, SingleObjectMixin, QuestionMixin, Redir
version_reject = VersionRejectView.as_view()
class VersionDiffView(GetVersionMixin, DetailView):
"""Show diff between two versions of a motion."""
permission_required = 'motion.can_see_motion'
model = Motion
template_name = 'motion/motion_diff.html'
def get_context_data(self, **kwargs):
"""Return the template context with versions and html diff strings."""
try:
rev1 = int(self.request.GET['rev1'])
rev2 = int(self.request.GET['rev2'])
version_rev1 = self.object.version.motion.versions.get(version_number=self.request.GET['rev1'])
version_rev2 = self.object.version.motion.versions.get(version_number=self.request.GET['rev2'])
diff_text = self.object.version.make_htmldiff(version_rev1.text, version_rev2.text)
diff_reason = self.object.version.make_htmldiff(version_rev1.reason, version_rev2.reason)
except (KeyError, ValueError, MotionVersion.DoesNotExist):
messages.error(self.request, _('At least one version number was not valid.'))
version_rev1 = None
version_rev2 = None
diff_text = None
diff_reason = None
context = super(VersionDiffView, self).get_context_data(**kwargs)
context.update({
'version_rev1': version_rev1,
'version_rev2': version_rev2,
'diff_text': diff_text,
'diff_reason': diff_reason,
})
return context
version_diff = VersionDiffView.as_view()
class SupportView(SingleObjectMixin, RedirectView):
"""View to support or unsupport a motion.