diff --git a/apps/documents/__init__.py b/apps/documents/__init__.py index 9326037834..2fd33a65a0 100644 --- a/apps/documents/__init__.py +++ b/apps/documents/__init__.py @@ -43,7 +43,7 @@ from .links import (document_page_transformation_list, document_page_transformat document_page_navigation_last, document_page_zoom_in, document_page_zoom_out, document_page_rotate_right, document_page_rotate_left, document_page_view_reset, document_multiple_clear_transformations, document_multiple_delete, - document_multiple_download) + document_multiple_download, document_version_text_compare) from .links import document_clear_image_cache from .statistics import get_statistics @@ -59,6 +59,7 @@ bind_links([Document], [document_view_simple, document_edit, document_print, doc # Document Version links bind_links([DocumentVersion], [document_version_revert, document_version_download]) +bind_links(['document_version_list', 'upload_version', 'document_version_revert', 'document_version_text_compare', 'document_version_show_diff_text'], [document_version_text_compare], menu_name='sidebar') secondary_menu_links = [document_list_recent, document_list] diff --git a/apps/documents/forms.py b/apps/documents/forms.py index 111ab80484..e39d45b29c 100644 --- a/apps/documents/forms.py +++ b/apps/documents/forms.py @@ -321,3 +321,33 @@ class DocumentDownloadForm(forms.Form): if len(self.document_versions) > 1: self.fields['compressed'].initial = True self.fields['compressed'].widget.attrs.update({'disabled': True}) + + +class DocumentVersionCompareForm(forms.Form): + left_version = forms.ChoiceField(label=_(u'Source version'), required=True) + right_version = forms.ChoiceField(label=_(u'Destination version'), required=True) + + def make_choices(self): + return [(version.pk, unicode(version)) for version in self.document.versions.all()] + + def __init__(self, *args, **kwargs): + self.document = kwargs.pop('document') + super(DocumentVersionCompareForm, self).__init__(*args, **kwargs) + choices = self.make_choices() + self.fields['left_version'].choices = choices + self.fields['left_version'].initial = choices[-2][0] if len(choices) > 1 else choices[-1][0] + + self.fields['right_version'].choices = choices + self.fields['right_version'].initial = choices[-1][0] + + +class DocumentVersionDiffForm(forms.Form): + def __init__(self, *args, **kwargs): + contents = kwargs.pop('contents', None) + super(DocumentVersionDiffForm, self).__init__(*args, **kwargs) + self.fields['contents'].initial = contents + + contents = forms.CharField( + label=_(u'Contents'), + widget=TextAreaDiv() + ) diff --git a/apps/documents/links.py b/apps/documents/links.py index 62fe8a33f6..7330bbd92c 100644 --- a/apps/documents/links.py +++ b/apps/documents/links.py @@ -11,7 +11,8 @@ from .permissions import (PERMISSION_DOCUMENT_CREATE, PERMISSION_DOCUMENT_TRANSFORM, PERMISSION_DOCUMENT_TOOLS, PERMISSION_DOCUMENT_EDIT, PERMISSION_DOCUMENT_VERSION_REVERT, PERMISSION_DOCUMENT_TYPE_EDIT, PERMISSION_DOCUMENT_TYPE_DELETE, - PERMISSION_DOCUMENT_TYPE_CREATE, PERMISSION_DOCUMENT_TYPE_VIEW) + PERMISSION_DOCUMENT_TYPE_CREATE, PERMISSION_DOCUMENT_TYPE_VIEW, + PERMISSION_DOCUMENT_VERSIONS_TEXT_COMPARE) from .conf.settings import ZOOM_MAX_LEVEL, ZOOM_MIN_LEVEL @@ -83,6 +84,7 @@ document_page_view_reset = Link(text=_(u'reset view'), klass='no-parent-history' # Document versions document_version_list = Link(text=_(u'versions'), view='document_version_list', args='object.pk', sprite='page_world', permissions=[PERMISSION_DOCUMENT_VIEW]) document_version_revert = Link(text=_(u'revert'), view='document_version_revert', args='object.pk', sprite='page_refresh', permissions=[PERMISSION_DOCUMENT_VERSION_REVERT], conditional_disable=is_current_version) +document_version_text_compare = Link(text=_(u'compare (text)'), view='document_version_text_compare', args='object.pk', sprite='table_relationship', permissions=[PERMISSION_DOCUMENT_VERSIONS_TEXT_COMPARE]) # Document type related links document_type_list = Link(text=_(u'document type list'), view='document_type_list', sprite='layout', permissions=[PERMISSION_DOCUMENT_TYPE_VIEW]) diff --git a/apps/documents/permissions.py b/apps/documents/permissions.py index 93d6bda51a..576a3a37c9 100644 --- a/apps/documents/permissions.py +++ b/apps/documents/permissions.py @@ -23,3 +23,5 @@ PERMISSION_DOCUMENT_TYPE_VIEW = Permission.objects.register(documents_setup_name PERMISSION_DOCUMENT_TYPE_EDIT = Permission.objects.register(documents_setup_namespace, 'document_type_edit', _(u'Edit document types')) PERMISSION_DOCUMENT_TYPE_DELETE = Permission.objects.register(documents_setup_namespace, 'document_type_delete', _(u'Delete document types')) PERMISSION_DOCUMENT_TYPE_CREATE = Permission.objects.register(documents_setup_namespace, 'document_type_create', _(u'Create document types')) + +PERMISSION_DOCUMENT_VERSIONS_TEXT_COMPARE = Permission.objects.register(documents_setup_namespace, 'document_versions_text_compare', _(u'Compare document version textually')) diff --git a/apps/documents/urls.py b/apps/documents/urls.py index 2a2cfcb7db..67708c097c 100644 --- a/apps/documents/urls.py +++ b/apps/documents/urls.py @@ -36,6 +36,8 @@ urlpatterns = patterns('documents.views', url(r'^(?P\d+)/version/all/$', 'document_version_list', (), 'document_version_list'), url(r'^document/version/(?P\d+)/download/$', 'document_download', (), 'document_version_download'), url(r'^document/version/(?P\d+)/revert/$', 'document_version_revert', (), 'document_version_revert'), + url(r'^document/(?P\d+)/version_compare/text/$', 'document_version_text_compare', (), 'document_version_text_compare'), + url(r'^document/version/show/text/diff/(?P\d+)/vs/(?P\d+)/$', 'document_version_show_diff_text', (), 'document_version_show_diff_text'), url(r'^multiple/clear_transformations/$', 'document_multiple_clear_transformations', (), 'document_multiple_clear_transformations'), url(r'^duplicates/list/$', 'document_find_all_duplicates', (), 'document_find_all_duplicates'), diff --git a/apps/documents/views.py b/apps/documents/views.py index 839bbb36f0..9744c4bb5d 100644 --- a/apps/documents/views.py +++ b/apps/documents/views.py @@ -40,17 +40,20 @@ from .permissions import (PERMISSION_DOCUMENT_CREATE, PERMISSION_DOCUMENT_TRANSFORM, PERMISSION_DOCUMENT_TOOLS, PERMISSION_DOCUMENT_EDIT, PERMISSION_DOCUMENT_VERSION_REVERT, PERMISSION_DOCUMENT_TYPE_EDIT, PERMISSION_DOCUMENT_TYPE_DELETE, - PERMISSION_DOCUMENT_TYPE_CREATE, PERMISSION_DOCUMENT_TYPE_VIEW) + PERMISSION_DOCUMENT_TYPE_CREATE, PERMISSION_DOCUMENT_TYPE_VIEW, + PERMISSION_DOCUMENT_VERSIONS_TEXT_COMPARE) from .events import history_document_edited from .forms import (DocumentForm_edit, DocumentPropertiesForm, DocumentPreviewForm, DocumentPageForm, DocumentPageTransformationForm, DocumentContentForm, DocumentPageForm_edit, DocumentPageForm_text, PrintForm, DocumentTypeForm, DocumentTypeFilenameForm, - DocumentTypeFilenameForm_create, DocumentDownloadForm) + DocumentTypeFilenameForm_create, DocumentDownloadForm, + DocumentVersionCompareForm, DocumentVersionDiffForm) from .models import (Document, DocumentType, DocumentPage, DocumentPageTransformation, RecentDocument, DocumentTypeFilename, DocumentVersion) +from .widgets import diff_widget logger = logging.getLogger(__name__) @@ -1364,3 +1367,60 @@ def document_version_revert(request, document_version_pk): 'message': _(u'All later version after this one will be deleted too.'), 'form_icon': u'page_refresh.png', }, context_instance=RequestContext(request)) + + +def document_version_text_compare(request, document_pk): + document = get_object_or_404(Document, pk=document_pk) + + try: + Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_VERSIONS_TEXT_COMPARE]) + except PermissionDenied: + AccessEntry.objects.check_access(PERMISSION_DOCUMENT_VERSIONS_TEXT_COMPARE, request.user, document) + + RecentDocument.objects.add_document_for_user(request.user, document) + + if request.method == 'POST': + form = DocumentVersionCompareForm(document=document, data=request.POST) + if form.is_valid(): + return HttpResponseRedirect(reverse('document_version_show_diff_text', args=[form.cleaned_data['left_version'], form.cleaned_data['right_version']])) + else: + form = DocumentVersionCompareForm(document=document) + + return render_to_response('generic_form.html', { + 'object': document, + 'title': _(u'Textually compare versions of document: %s') % document, + 'form': form, + 'submit_label': _(u'Compare'), + 'submit_icon_famfam': 'table_relationship', + }, + context_instance=RequestContext(request)) + + +def document_version_show_diff_text(request, left_document_version_pk, right_document_version_pk): + left_document_version = get_object_or_404(DocumentVersion, pk=left_document_version_pk) + right_document_version = get_object_or_404(DocumentVersion, pk=right_document_version_pk) + + try: + Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_VERSIONS_TEXT_COMPARE]) + except PermissionDenied: + # Make sure user has the compare permission for both document + # versions' root documents + AccessEntry.objects.check_access(PERMISSION_DOCUMENT_VERSIONS_TEXT_COMPARE, request.user, left_document_version.document) + AccessEntry.objects.check_access(PERMISSION_DOCUMENT_VERSIONS_TEXT_COMPARE, request.user, right_document_version.document) + + context = { + 'form': DocumentVersionDiffForm(contents=diff_widget(left_document_version.content, right_document_version.content)), + 'read_only': True + } + + if left_document_version.document == right_document_version.document: + context['object'] = left_document_version.document + context['title'] = _(u'Textual difference between version %(version1)s and %(version2)s of document: %(document)s') % { + 'version1': left_document_version, 'version2': right_document_version, + 'document': left_document_version.document} + else: + context['title'] = _(u'Textual difference between document: %(document1)s and %(document2)s.') % { + 'document1': left_document_version.document, 'document2': right_document_version.document} + + return render_to_response('generic_form.html', context, + context_instance=RequestContext(request)) diff --git a/apps/documents/widgets.py b/apps/documents/widgets.py index 705c28cd82..a7b46294d3 100644 --- a/apps/documents/widgets.py +++ b/apps/documents/widgets.py @@ -1,5 +1,7 @@ from __future__ import absolute_import +import diff_match_patch + from django.utils.safestring import mark_safe from django.conf import settings from django.utils.translation import ugettext_lazy as _ @@ -85,3 +87,18 @@ def document_html_widget(document, view='document_thumbnail', click_view=None, p ) return mark_safe(u''.join(result)) + + +def diff_widget(version1, version2): + + dmp = diff_match_patch.diff_match_patch() + dmp.Diff_Timeout = 1 # Don't spend more than 1 seconds on a diff. + + differences = dmp.diff_main(version1, version2) + dmp.diff_cleanupSemantic(differences) + pretty = dmp.diff_prettyHtml(differences) + #patch=dmp.patch_make(differences) + #text = dmp.patch_toText(patch) + return mark_safe(dmp.diff_prettyHtml(differences)) + + diff --git a/apps/sources/__init__.py b/apps/sources/__init__.py index 6ed77c392b..64db8aeaab 100644 --- a/apps/sources/__init__.py +++ b/apps/sources/__init__.py @@ -48,7 +48,7 @@ bind_links([WatchFolder], [setup_web_form_list, setup_staging_folder_list, setup bind_links([WatchFolder], [setup_source_transformation_list, setup_source_edit, setup_source_delete]) # Document version -bind_links(['document_version_list', 'upload_version', 'document_version_revert'], [upload_version], menu_name='sidebar') +bind_links(['document_version_list', 'upload_version', 'document_version_revert', 'document_version_text_compare', 'document_version_show_diff_text'], [upload_version], menu_name='sidebar') bind_links(['setup_source_transformation_create', 'setup_source_transformation_edit', 'setup_source_transformation_delete', 'setup_source_transformation_list'], [setup_source_transformation_create], menu_name='sidebar') diff --git a/requirements/production.txt b/requirements/production.txt index 883346078d..9055a27f2f 100644 --- a/requirements/production.txt +++ b/requirements/production.txt @@ -72,3 +72,4 @@ GitPython==0.3.2.RC1 elementtree==1.2.7-20070827-preview Pygments==1.5 +diff-match-patch==20120106