diff --git a/.tx/config b/.tx/config index 0ee87646bb..751a29b59d 100644 --- a/.tx/config +++ b/.tx/config @@ -176,3 +176,11 @@ trans.es = apps/django_gpg/locale/es/LC_MESSAGES/django.po trans.pt = apps/django_gpg/locale/pt/LC_MESSAGES/django.po trans.ru = apps/django_gpg/locale/ru/LC_MESSAGES/django.po trans.it = apps/django_gpg/locale/it/LC_MESSAGES/django.po + +[mayan-edms.apps-document_signatures] +source_file = apps/document_signatures/locale/en/LC_MESSAGES/django.po +source_lang = en +trans.es = apps/document_signatures/locale/es/LC_MESSAGES/django.po +trans.pt = apps/document_signatures/locale/pt/LC_MESSAGES/django.po +trans.ru = apps/document_signatures/locale/ru/LC_MESSAGES/django.po +trans.it = apps/document_signatures/locale/it/LC_MESSAGES/django.po diff --git a/apps/django_gpg/__init__.py b/apps/django_gpg/__init__.py index 3cebb1c5ad..11f64783d0 100644 --- a/apps/django_gpg/__init__.py +++ b/apps/django_gpg/__init__.py @@ -1,6 +1,5 @@ from django.utils.translation import ugettext_lazy as _ -#from documents.models import Document from navigation.api import register_links, register_top_menu, \ register_model_list_columns, register_multi_item_links, \ register_sidebar_template @@ -11,21 +10,12 @@ from hkp import Key as KeyServerKey from django_gpg.api import Key -django_gpg_namespace = PermissionNamespace('django_gpg', _(u'Signatures')) +django_gpg_namespace = PermissionNamespace('django_gpg', _(u'Key management')) -PERMISSION_DOCUMENT_VERIFY = Permission.objects.register(django_gpg_namespace, 'document_verify', _(u'Verify document signatures')) PERMISSION_KEY_VIEW = Permission.objects.register(django_gpg_namespace, 'key_view', _(u'View keys')) PERMISSION_KEY_DELETE = Permission.objects.register(django_gpg_namespace, 'key_delete', _(u'Delete keys')) PERMISSION_KEYSERVER_QUERY = Permission.objects.register(django_gpg_namespace, 'keyserver_query', _(u'Query keyservers')) PERMISSION_KEY_RECEIVE = Permission.objects.register(django_gpg_namespace, 'key_receive', _(u'Import keys from keyservers')) -PERMISSION_SIGNATURE_UPLOAD = Permission.objects.register(django_gpg_namespace, 'signature_upload', _(u'Upload detached signatures')) -PERMISSION_SIGNATURE_DOWNLOAD = Permission.objects.register(django_gpg_namespace, 'signature_download', _(u'Download detached signatures')) - -def has_embedded_signature(context): - return context['object'].signature_state - -def doesnt_have_detached_signature(context): - return context['object'].has_detached_signature() == False # Setup views private_keys = {'text': _(u'private keys'), 'view': 'key_private_list', 'args': 'object.pk', 'famfam': 'key', 'icon': 'key.png', 'permissions': [PERMISSION_KEY_VIEW]} @@ -33,17 +23,8 @@ public_keys = {'text': _(u'public keys'), 'view': 'key_public_list', 'args': 'ob key_delete = {'text': _(u'delete'), 'view': 'key_delete', 'args': ['object.fingerprint', 'object.type'], 'famfam': 'key_delete', 'permissions': [PERMISSION_KEY_DELETE]} key_query = {'text': _(u'query keyservers'), 'view': 'key_query', 'famfam': 'zoom', 'permissions': [PERMISSION_KEYSERVER_QUERY]} key_receive = {'text': _(u'import'), 'view': 'key_receive', 'args': 'object.keyid', 'famfam': 'key_add', 'keep_query': True, 'permissions': [PERMISSION_KEY_RECEIVE]} -document_signature_upload = {'text': _(u'upload signature'), 'view': 'document_signature_upload', 'args': 'object.pk', 'famfam': 'pencil_add', 'permissions': [PERMISSION_SIGNATURE_UPLOAD], 'conditional_disable': has_embedded_signature} -document_signature_download = {'text': _(u'download signature'), 'view': 'document_signature_download', 'args': 'object.pk', 'famfam': 'disk', 'permissions': [PERMISSION_SIGNATURE_DOWNLOAD], 'conditional_disable': doesnt_have_detached_signature} key_setup = {'text': _(u'key management'), 'view': 'key_public_list', 'args': 'object.pk', 'famfam': 'key', 'icon': 'key.png', 'permissions': [PERMISSION_KEY_VIEW]} -# Document views -document_verify = {'text': _(u'signatures'), 'view': 'document_verify', 'args': 'object.pk', 'famfam': 'text_signature', 'permissions': [PERMISSION_DOCUMENT_VERIFY]} - -#register_links(Document, [document_verify], menu_name='form_header') - -register_links(['document_verify', 'document_signature_upload', 'document_signature_download'], [document_signature_upload, document_signature_download], menu_name='sidebar') - #register_links(['key_delete', 'key_private_list', 'key_public_list', 'key_query'], [private_keys, public_keys, key_query], menu_name='sidebar') register_links(['key_delete', 'key_public_list', 'key_query'], [public_keys, key_query], menu_name='sidebar') diff --git a/apps/django_gpg/api.py b/apps/django_gpg/api.py index 8d9f2e2b4b..af95cc924f 100644 --- a/apps/django_gpg/api.py +++ b/apps/django_gpg/api.py @@ -1,5 +1,5 @@ import types -from StringIO import StringIO + from pickle import dumps import logging import tempfile @@ -151,6 +151,17 @@ class Key(object): class GPG(object): + @staticmethod + def get_descriptor(file_input): + try: + # Is it a file like object? + file_input.seek(0) + except AttributeError: + # If not, try open it. + return open(file_input, 'rb') + else: + return file_input + def __init__(self, binary_path=None, home=None, keyring=None, keyservers=None): kwargs = {} if binary_path: @@ -167,52 +178,46 @@ class GPG(object): self.gpg = gnupg.GPG(**kwargs) def verify_w_retry(self, file_input, detached_signature=None): - if isinstance(file_input, types.StringTypes): - input_descriptor = open(file_input, 'rb') - elif isinstance(file_input, types.FileType) or isinstance(file_input, File): - input_descriptor = file_input - elif issubclass(file_input.__class__, StringIO): - input_descriptor = file_input - else: - raise ValueError('Invalid file_input argument type') + logger.debug('file_input type: %s' % type(file_input)) + + input_descriptor = GPG.get_descriptor(file_input) try: - verify = self.verify_file(input_descriptor, detached_signature) + verify = self.verify_file(input_descriptor, detached_signature, close_descriptor=False) if verify.status == 'no public key': # Try to fetch the public key from the keyservers try: self.receive_key(verify.key_id) - return self.verify_w_retry(file_input, detached_signature) + return self.verify_w_retry(input_descriptor, detached_signature) except KeyFetchingError: return verify else: + input_descriptor.close() return verify except IOError: return False - def verify_file(self, file_input, detached_signature=None): - """ + def verify_file(self, file_input, detached_signature=None, close_descriptor=True): + ''' Verify the signature of a file. - """ - if isinstance(file_input, types.StringTypes): - descriptor = open(file_input, 'rb') - elif isinstance(file_input, types.FileType) or isinstance(file_input, File) or isinstance(file_input, StringIO): - descriptor = file_input - else: - raise ValueError('Invalid file_input argument type') + ''' + input_descriptor = GPG.get_descriptor(file_input) + if detached_signature: # Save the original data and invert the argument order # Signature first, file second file_descriptor, filename = tempfile.mkstemp(prefix='django_gpg') - file_data = file_input.read() + file_data = input_descriptor.read() file_input.close() os.write(file_descriptor, file_data) os.close(file_descriptor) verify = self.gpg.verify_file(detached_signature, data_filename=filename) else: - verify = self.gpg.verify_file(descriptor) - descriptor.close() + verify = self.gpg.verify_file(input_descriptor) + + if close_descriptor: + input_descriptor.close() if verify: return verify @@ -232,12 +237,13 @@ class GPG(object): raise GPGVerificationError(verify.status) def sign_file(self, file_input, key=None, destination=None, key_id=None, passphrase=None, clearsign=False): - """ + ''' Signs a filename, storing the signature and the original file in the destination filename provided (the destination file is overrided if it already exists), if no destination file name is provided the signature is returned. - """ + ''' + kwargs = {} kwargs['clearsign'] = clearsign @@ -250,14 +256,7 @@ class GPG(object): if passphrase: kwargs['passphrase'] = passphrase - if isinstance(file_input, types.StringTypes): - input_descriptor = open(file_input, 'rb') - elif isinstance(file_input, types.FileType) or isinstance(file_input, File): - input_descriptor = file_input - elif issubclass(file_input.__class__, StringIO): - input_descriptor = file_input - else: - raise ValueError('Invalid file_input argument type') + input_descriptor = GPG.get_descriptor(file_input) if destination: output_descriptor = open(destination, 'wb') @@ -277,16 +276,13 @@ class GPG(object): if not destination: return signed_data - def decrypt_file(self, file_input): - if isinstance(file_input, types.StringTypes): - input_descriptor = open(file_input, 'rb') - elif isinstance(file_input, types.FileType) or isinstance(file_input, File) or isinstance(file_input, StringIO): - input_descriptor = file_input - else: - raise ValueError('Invalid file_input argument type') + def decrypt_file(self, file_input, close_descriptor=True): + input_descriptor = GPG.get_descriptor(file_input) result = self.gpg.decrypt_file(input_descriptor) - input_descriptor.close() + if close_descriptor: + input_descriptor.close() + if not result.status: raise GPGDecryptionError('Unable to decrypt file') diff --git a/apps/django_gpg/forms.py b/apps/django_gpg/forms.py index b961daf037..619035fd5d 100644 --- a/apps/django_gpg/forms.py +++ b/apps/django_gpg/forms.py @@ -11,9 +11,3 @@ class KeySearchForm(forms.Form): label=_(u'Term'), help_text=_(u'Name, e-mail, key ID or key fingerprint to look for.') ) - - -class DetachedSignatureForm(forms.Form): - file = forms.FileField( - label=_(u'Signature file'), - ) diff --git a/apps/django_gpg/urls.py b/apps/django_gpg/urls.py index 4a22882d06..04ef8af89c 100644 --- a/apps/django_gpg/urls.py +++ b/apps/django_gpg/urls.py @@ -4,9 +4,6 @@ urlpatterns = patterns('django_gpg.views', url(r'^delete/(?P.+)/(?P\w+)/$', 'key_delete', (), 'key_delete'), url(r'^list/private/$', 'key_list', {'secret': True}, 'key_private_list'), url(r'^list/public/$', 'key_list', {'secret': False}, 'key_public_list'), - url(r'^verify/(?P\d+)/$', 'document_verify', (), 'document_verify'), - url(r'^upload/signature/(?P\d+)/$', 'document_signature_upload', (), 'document_signature_upload'), - url(r'^download/signature/(?P\d+)/$', 'document_signature_download', (), 'document_signature_download'), url(r'^query/$', 'key_query', (), 'key_query'), url(r'^receive/(?P.+)/$', 'key_receive', (), 'key_receive'), ) diff --git a/apps/django_gpg/views.py b/apps/django_gpg/views.py index 6a1a334a25..83fa80cfd7 100644 --- a/apps/django_gpg/views.py +++ b/apps/django_gpg/views.py @@ -11,21 +11,17 @@ from django.utils.safestring import mark_safe from django.conf import settings from django.template.defaultfilters import force_escape -from documents.models import Document, RecentDocument from permissions.models import Permission from common.utils import pretty_size, parse_range, urlquote, \ return_diff, encapsulate -from filetransfers.api import serve_file from django_gpg.api import Key, SIGNATURE_STATES from django_gpg.runtime import gpg from django_gpg.exceptions import (GPGVerificationError, KeyFetchingError, KeyImportError) -from django_gpg import (PERMISSION_DOCUMENT_VERIFY, PERMISSION_KEY_VIEW, - PERMISSION_KEY_DELETE, PERMISSION_KEYSERVER_QUERY, - PERMISSION_KEY_RECEIVE, PERMISSION_SIGNATURE_UPLOAD, - PERMISSION_SIGNATURE_DOWNLOAD) -from django_gpg.forms import KeySearchForm, DetachedSignatureForm +from django_gpg import (PERMISSION_KEY_VIEW, PERMISSION_KEY_DELETE, + PERMISSION_KEYSERVER_QUERY, PERMISSION_KEY_RECEIVE) +from django_gpg.forms import KeySearchForm logger = logging.getLogger(__name__) @@ -189,98 +185,3 @@ def key_query(request): return render_to_response('generic_form.html', { 'subtemplates_list': subtemplates_list, }, context_instance=RequestContext(request)) - - -def document_verify(request, document_pk): - Permission.objects.check_permissions(request.user, [PERMISSION_DOCUMENT_VERIFY]) - document = get_object_or_404(Document, pk=document_pk) - - RecentDocument.objects.add_document_for_user(request.user, document) - - signature = document.verify_signature() - - signature_state = SIGNATURE_STATES.get(getattr(signature, 'status', None)) - - widget = (u'' % (settings.STATIC_URL, signature_state['icon'])) - paragraphs = [ - _(u'Signature status: %(widget)s %(text)s') % { - 'widget': mark_safe(widget), - 'text': signature_state['text'] - }, - ] - - if document.signature_state: - signature_type = _(u'embedded') - else: - signature_type = _(u'detached') - - if signature: - paragraphs.extend( - [ - _(u'Signature ID: %s') % signature.signature_id, - _(u'Signature type: %s') % signature_type, - _(u'Key ID: %s') % signature.key_id, - _(u'Timestamp: %s') % datetime.fromtimestamp(int(signature.sig_timestamp)), - _(u'Signee: %s') % force_escape(getattr(signature, 'username', u'')), - ] - ) - - return render_to_response('generic_template.html', { - 'title': _(u'signature properties for: %s') % document, - 'object': document, - 'document': document, - 'paragraphs': paragraphs, - }, context_instance=RequestContext(request)) - - -def document_signature_upload(request, document_pk): - Permission.objects.check_permissions(request.user, [PERMISSION_SIGNATURE_UPLOAD]) - document = get_object_or_404(Document, pk=document_pk) - - RecentDocument.objects.add_document_for_user(request.user, document) - - post_action_redirect = None - previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', '/'))) - next = request.POST.get('next', request.GET.get('next', post_action_redirect if post_action_redirect else request.META.get('HTTP_REFERER', '/'))) - - if request.method == 'POST': - form = DetachedSignatureForm(request.POST, request.FILES) - if form.is_valid(): - try: - document.add_detached_signature(request.FILES['file']) - messages.success(request, _(u'Detached signature uploaded successfully.')) - return HttpResponseRedirect(next) - except Exception, msg: - messages.error(request, msg) - return HttpResponseRedirect(previous) - else: - form = DetachedSignatureForm() - - return render_to_response('generic_form.html', { - 'title': _(u'Upload detached signature for: %s') % document, - 'form_icon': 'key_delete.png', - 'next': next, - 'form': form, - 'previous': previous, - 'object': document, - }, context_instance=RequestContext(request)) - - -def document_signature_download(request, document_pk): - Permission.objects.check_permissions(request.user, [PERMISSION_SIGNATURE_DOWNLOAD]) - document = get_object_or_404(Document, pk=document_pk) - - try: - if document.has_detached_signature(): - signature = document.detached_signature() - return serve_file( - request, - signature, - save_as=u'"%s.sig"' % document.filename, - content_type=u'application/octet-stream' - ) - except Exception, e: - messages.error(request, e) - return HttpResponseRedirect(request.META['HTTP_REFERER']) - - return HttpResponseRedirect(request.META['HTTP_REFERER']) diff --git a/apps/document_comments/__init__.py b/apps/document_comments/__init__.py index 895d96e1ed..a0de601ea1 100644 --- a/apps/document_comments/__init__.py +++ b/apps/document_comments/__init__.py @@ -1,6 +1,7 @@ from django.utils.translation import ugettext_lazy as _ from django.conf import settings from django.contrib.comments.models import Comment +from django.contrib.contenttypes import generic from navigation.api import register_links, register_model_list_columns from permissions.models import PermissionNamespace, Permission @@ -41,3 +42,12 @@ register_model_list_columns(Comment, [ register_links(['comments_for_object', 'comment_add', 'comment_delete', 'comment_multiple_delete'], [comment_add], menu_name='sidebar') register_links(Comment, [comment_delete]) register_links(Document, [comments_for_object], menu_name='form_header') + +Document.add_to_class( + 'comments', + generic.GenericRelation( + Comment, + content_type_field='content_type', + object_id_field='object_pk' + ) +) diff --git a/apps/document_signatures/__init__.py b/apps/document_signatures/__init__.py new file mode 100644 index 0000000000..0a34ed064d --- /dev/null +++ b/apps/document_signatures/__init__.py @@ -0,0 +1,61 @@ +from __future__ import absolute_import +import logging + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +from django.utils.translation import ugettext_lazy as _ +from django.db.models.signals import post_save + +from documents.models import Document, DocumentVersion +from navigation.api import register_links + +from django_gpg.runtime import gpg +from django_gpg.exceptions import GPGDecryptionError + +from .models import DocumentVersionSignature +from .permissions import ( + PERMISSION_DOCUMENT_VERIFY, + PERMISSION_SIGNATURE_UPLOAD, + PERMISSION_SIGNATURE_DOWNLOAD +) + +logger = logging.getLogger(__name__) + + +def has_embedded_signature(context): + return DocumentVersionSignature.objects.has_embedded_signature(context['object']) + + +def doesnt_have_detached_signature(context): + return DocumentVersionSignature.objects.has_detached_signature(context['object']) == False + + +def document_pre_open_hook(descriptor): + try: + result = gpg.decrypt_file(descriptor, close_descriptor=False) + # gpg return a string, turn it into a file like object + except GPGDecryptionError: + # At least return the original raw content + descriptor.seek(0) + return descriptor + else: + return StringIO(result.data) + + +def document_post_save(sender, instance, **kwargs): + if kwargs.get('created', False): + DocumentVersionSignature.objects.signature_state(instance.document) + +document_signature_upload = {'text': _(u'upload signature'), 'view': 'document_signature_upload', 'args': 'object.pk', 'famfam': 'pencil_add', 'permissions': [PERMISSION_SIGNATURE_UPLOAD], 'conditional_disable': has_embedded_signature} +document_signature_download = {'text': _(u'download signature'), 'view': 'document_signature_download', 'args': 'object.pk', 'famfam': 'disk', 'permissions': [PERMISSION_SIGNATURE_DOWNLOAD], 'conditional_disable': doesnt_have_detached_signature} +document_verify = {'text': _(u'signatures'), 'view': 'document_verify', 'args': 'object.pk', 'famfam': 'text_signature', 'permissions': [PERMISSION_DOCUMENT_VERIFY]} + +register_links(Document, [document_verify], menu_name='form_header') +register_links(['document_verify', 'document_signature_upload', 'document_signature_download'], [document_signature_upload, document_signature_download], menu_name='sidebar') + +DocumentVersion.register_pre_open_hook(1, document_pre_open_hook) + +post_save.connect(document_post_save, sender=DocumentVersion) diff --git a/apps/document_signatures/forms.py b/apps/document_signatures/forms.py new file mode 100644 index 0000000000..7e3f568b1b --- /dev/null +++ b/apps/document_signatures/forms.py @@ -0,0 +1,12 @@ +from django import forms +from django.utils.translation import ugettext_lazy as _ +#from django.utils.translation import ugettext +#from django.core.urlresolvers import reverse +#from django.utils.safestring import mark_safe +#from django.conf import settings + + +class DetachedSignatureForm(forms.Form): + file = forms.FileField( + label=_(u'Signature file'), + ) diff --git a/apps/document_signatures/locale/en/LC_MESSAGES/django.po b/apps/document_signatures/locale/en/LC_MESSAGES/django.po new file mode 100644 index 0000000000..f2c2ac4175 --- /dev/null +++ b/apps/document_signatures/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,122 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-01-01 20:14-0400\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: __init__.py:50 +msgid "upload signature" +msgstr "" + +#: __init__.py:51 +msgid "download signature" +msgstr "" + +#: __init__.py:52 +msgid "signatures" +msgstr "" + +#: forms.py:11 +msgid "Signature file" +msgstr "" + +#: models.py:19 +msgid "document version" +msgstr "" + +#: models.py:20 +msgid "signature state" +msgstr "" + +#: models.py:21 +msgid "signature file" +msgstr "" + +#: models.py:26 +msgid "document version signature" +msgstr "" + +#: models.py:27 +msgid "document version signatures" +msgstr "" + +#: permissions.py:7 +msgid "Verify document signatures" +msgstr "" + +#: permissions.py:8 +msgid "Upload detached signatures" +msgstr "" + +#: permissions.py:9 +msgid "Download detached signatures" +msgstr "" + +#: permissions.py:12 +msgid "Document signatures" +msgstr "" + +#: views.py:47 +#, python-format +msgid "Signature status: %(widget)s %(text)s" +msgstr "" + +#: views.py:54 +msgid "embedded" +msgstr "" + +#: views.py:56 +msgid "detached" +msgstr "" + +#: views.py:61 +#, python-format +msgid "Signature ID: %s" +msgstr "" + +#: views.py:62 +#, python-format +msgid "Signature type: %s" +msgstr "" + +#: views.py:63 +#, python-format +msgid "Key ID: %s" +msgstr "" + +#: views.py:64 +#, python-format +msgid "Timestamp: %s" +msgstr "" + +#: views.py:65 +#, python-format +msgid "Signee: %s" +msgstr "" + +#: views.py:70 +#, python-format +msgid "signature properties for: %s" +msgstr "" + +#: views.py:92 +msgid "Detached signature uploaded successfully." +msgstr "" + +#: views.py:101 +#, python-format +msgid "Upload detached signature for: %s" +msgstr "" diff --git a/apps/document_signatures/locale/es/LC_MESSAGES/django.po b/apps/document_signatures/locale/es/LC_MESSAGES/django.po new file mode 100644 index 0000000000..6aaa824359 --- /dev/null +++ b/apps/document_signatures/locale/es/LC_MESSAGES/django.po @@ -0,0 +1,123 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-01-01 20:14-0400\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" + +#: __init__.py:50 +msgid "upload signature" +msgstr "" + +#: __init__.py:51 +msgid "download signature" +msgstr "" + +#: __init__.py:52 +msgid "signatures" +msgstr "" + +#: forms.py:11 +msgid "Signature file" +msgstr "" + +#: models.py:19 +msgid "document version" +msgstr "" + +#: models.py:20 +msgid "signature state" +msgstr "" + +#: models.py:21 +msgid "signature file" +msgstr "" + +#: models.py:26 +msgid "document version signature" +msgstr "" + +#: models.py:27 +msgid "document version signatures" +msgstr "" + +#: permissions.py:7 +msgid "Verify document signatures" +msgstr "" + +#: permissions.py:8 +msgid "Upload detached signatures" +msgstr "" + +#: permissions.py:9 +msgid "Download detached signatures" +msgstr "" + +#: permissions.py:12 +msgid "Document signatures" +msgstr "" + +#: views.py:47 +#, python-format +msgid "Signature status: %(widget)s %(text)s" +msgstr "" + +#: views.py:54 +msgid "embedded" +msgstr "" + +#: views.py:56 +msgid "detached" +msgstr "" + +#: views.py:61 +#, python-format +msgid "Signature ID: %s" +msgstr "" + +#: views.py:62 +#, python-format +msgid "Signature type: %s" +msgstr "" + +#: views.py:63 +#, python-format +msgid "Key ID: %s" +msgstr "" + +#: views.py:64 +#, python-format +msgid "Timestamp: %s" +msgstr "" + +#: views.py:65 +#, python-format +msgid "Signee: %s" +msgstr "" + +#: views.py:70 +#, python-format +msgid "signature properties for: %s" +msgstr "" + +#: views.py:92 +msgid "Detached signature uploaded successfully." +msgstr "" + +#: views.py:101 +#, python-format +msgid "Upload detached signature for: %s" +msgstr "" diff --git a/apps/document_signatures/locale/it/LC_MESSAGES/django.po b/apps/document_signatures/locale/it/LC_MESSAGES/django.po new file mode 100644 index 0000000000..6aaa824359 --- /dev/null +++ b/apps/document_signatures/locale/it/LC_MESSAGES/django.po @@ -0,0 +1,123 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-01-01 20:14-0400\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" + +#: __init__.py:50 +msgid "upload signature" +msgstr "" + +#: __init__.py:51 +msgid "download signature" +msgstr "" + +#: __init__.py:52 +msgid "signatures" +msgstr "" + +#: forms.py:11 +msgid "Signature file" +msgstr "" + +#: models.py:19 +msgid "document version" +msgstr "" + +#: models.py:20 +msgid "signature state" +msgstr "" + +#: models.py:21 +msgid "signature file" +msgstr "" + +#: models.py:26 +msgid "document version signature" +msgstr "" + +#: models.py:27 +msgid "document version signatures" +msgstr "" + +#: permissions.py:7 +msgid "Verify document signatures" +msgstr "" + +#: permissions.py:8 +msgid "Upload detached signatures" +msgstr "" + +#: permissions.py:9 +msgid "Download detached signatures" +msgstr "" + +#: permissions.py:12 +msgid "Document signatures" +msgstr "" + +#: views.py:47 +#, python-format +msgid "Signature status: %(widget)s %(text)s" +msgstr "" + +#: views.py:54 +msgid "embedded" +msgstr "" + +#: views.py:56 +msgid "detached" +msgstr "" + +#: views.py:61 +#, python-format +msgid "Signature ID: %s" +msgstr "" + +#: views.py:62 +#, python-format +msgid "Signature type: %s" +msgstr "" + +#: views.py:63 +#, python-format +msgid "Key ID: %s" +msgstr "" + +#: views.py:64 +#, python-format +msgid "Timestamp: %s" +msgstr "" + +#: views.py:65 +#, python-format +msgid "Signee: %s" +msgstr "" + +#: views.py:70 +#, python-format +msgid "signature properties for: %s" +msgstr "" + +#: views.py:92 +msgid "Detached signature uploaded successfully." +msgstr "" + +#: views.py:101 +#, python-format +msgid "Upload detached signature for: %s" +msgstr "" diff --git a/apps/document_signatures/locale/pt/LC_MESSAGES/django.po b/apps/document_signatures/locale/pt/LC_MESSAGES/django.po new file mode 100644 index 0000000000..6aaa824359 --- /dev/null +++ b/apps/document_signatures/locale/pt/LC_MESSAGES/django.po @@ -0,0 +1,123 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-01-01 20:14-0400\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1)\n" + +#: __init__.py:50 +msgid "upload signature" +msgstr "" + +#: __init__.py:51 +msgid "download signature" +msgstr "" + +#: __init__.py:52 +msgid "signatures" +msgstr "" + +#: forms.py:11 +msgid "Signature file" +msgstr "" + +#: models.py:19 +msgid "document version" +msgstr "" + +#: models.py:20 +msgid "signature state" +msgstr "" + +#: models.py:21 +msgid "signature file" +msgstr "" + +#: models.py:26 +msgid "document version signature" +msgstr "" + +#: models.py:27 +msgid "document version signatures" +msgstr "" + +#: permissions.py:7 +msgid "Verify document signatures" +msgstr "" + +#: permissions.py:8 +msgid "Upload detached signatures" +msgstr "" + +#: permissions.py:9 +msgid "Download detached signatures" +msgstr "" + +#: permissions.py:12 +msgid "Document signatures" +msgstr "" + +#: views.py:47 +#, python-format +msgid "Signature status: %(widget)s %(text)s" +msgstr "" + +#: views.py:54 +msgid "embedded" +msgstr "" + +#: views.py:56 +msgid "detached" +msgstr "" + +#: views.py:61 +#, python-format +msgid "Signature ID: %s" +msgstr "" + +#: views.py:62 +#, python-format +msgid "Signature type: %s" +msgstr "" + +#: views.py:63 +#, python-format +msgid "Key ID: %s" +msgstr "" + +#: views.py:64 +#, python-format +msgid "Timestamp: %s" +msgstr "" + +#: views.py:65 +#, python-format +msgid "Signee: %s" +msgstr "" + +#: views.py:70 +#, python-format +msgid "signature properties for: %s" +msgstr "" + +#: views.py:92 +msgid "Detached signature uploaded successfully." +msgstr "" + +#: views.py:101 +#, python-format +msgid "Upload detached signature for: %s" +msgstr "" diff --git a/apps/document_signatures/locale/ru/LC_MESSAGES/django.po b/apps/document_signatures/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 0000000000..c0c6309907 --- /dev/null +++ b/apps/document_signatures/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,124 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-01-01 20:14-0400\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%" +"10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" + +#: __init__.py:50 +msgid "upload signature" +msgstr "" + +#: __init__.py:51 +msgid "download signature" +msgstr "" + +#: __init__.py:52 +msgid "signatures" +msgstr "" + +#: forms.py:11 +msgid "Signature file" +msgstr "" + +#: models.py:19 +msgid "document version" +msgstr "" + +#: models.py:20 +msgid "signature state" +msgstr "" + +#: models.py:21 +msgid "signature file" +msgstr "" + +#: models.py:26 +msgid "document version signature" +msgstr "" + +#: models.py:27 +msgid "document version signatures" +msgstr "" + +#: permissions.py:7 +msgid "Verify document signatures" +msgstr "" + +#: permissions.py:8 +msgid "Upload detached signatures" +msgstr "" + +#: permissions.py:9 +msgid "Download detached signatures" +msgstr "" + +#: permissions.py:12 +msgid "Document signatures" +msgstr "" + +#: views.py:47 +#, python-format +msgid "Signature status: %(widget)s %(text)s" +msgstr "" + +#: views.py:54 +msgid "embedded" +msgstr "" + +#: views.py:56 +msgid "detached" +msgstr "" + +#: views.py:61 +#, python-format +msgid "Signature ID: %s" +msgstr "" + +#: views.py:62 +#, python-format +msgid "Signature type: %s" +msgstr "" + +#: views.py:63 +#, python-format +msgid "Key ID: %s" +msgstr "" + +#: views.py:64 +#, python-format +msgid "Timestamp: %s" +msgstr "" + +#: views.py:65 +#, python-format +msgid "Signee: %s" +msgstr "" + +#: views.py:70 +#, python-format +msgid "signature properties for: %s" +msgstr "" + +#: views.py:92 +msgid "Detached signature uploaded successfully." +msgstr "" + +#: views.py:101 +#, python-format +msgid "Upload detached signature for: %s" +msgstr "" diff --git a/apps/document_signatures/managers.py b/apps/document_signatures/managers.py new file mode 100644 index 0000000000..f4ed8c28f9 --- /dev/null +++ b/apps/document_signatures/managers.py @@ -0,0 +1,91 @@ +import logging + +from django.db import models + +from django_gpg.runtime import gpg +from django_gpg.exceptions import GPGVerificationError + +logger = logging.getLogger(__name__) + + +class DocumentVersionSignatureManager(models.Manager): + #def update_signed_state(self, document): + # document_signature, created = self.model.get_or_create( + # document_version=document.latest_version, + # ) + # if document.exists(): + # descriptor = document.open() + # try: + # document_signature.signature_state = gpg.verify_file(descriptor).status + # # TODO: give use choice for auto public key fetch? + # # OR maybe new config option + # except GPGVerificationError: + # document_signature.signature_state = None + # finally: + # document_signature.save() + + def add_detached_signature(self, document, detached_signature): + document_signature, created = self.model.objects.get_or_create( + document_version=document.latest_version, + ) + if not self.signature_state(document): + document_signature.signature_file = detached_signature + document_signature.save() + else: + raise Exception('document already has an embedded signature') + + def has_detached_signature(self, document): + document_signature, created = self.model.objects.get_or_create( + document_version=document.latest_version, + ) + if document_signature.signature_file: + return True + else: + return False + + def has_embedded_signature(self, document): + logger.debug('document: %s' % document) + + if self.signature_state(document): + return True + else: + return False + + def signature_state(self, document): + document_signature, created = self.model.objects.get_or_create( + document_version=document.latest_version, + ) + logger.debug('created: %s' % created) + if created and document.exists(): + descriptor = document.open(raw=True) + try: + document_signature.signature_state = gpg.verify_file(descriptor).status + # TODO: give use choice for auto public key fetch? + # OR maybe new config option + except GPGVerificationError: + document_signature.signature_state = None + finally: + document_signature.save() + + #document_signature.signature_state = self.verify_signature(document).status + #document_signature.save() + + return document_signature.signature_state + + def detached_signature(self, document): + document_signature, created = self.model.objects.get_or_create( + document_version=document.latest_version, + ) + return document_signature.signature_file.storage.open(document_signature.signature_file.path) + + def verify_signature(self, document): + if self.has_detached_signature(document): + logger.debug('has detached signature') + args = (document.open(raw=True), self.detached_signature(document)) + else: + args = (document.open(raw=True),) + + try: + return gpg.verify_w_retry(*args) + except GPGVerificationError: + return None diff --git a/apps/document_signatures/migrations/0001_initial.py b/apps/document_signatures/migrations/0001_initial.py new file mode 100644 index 0000000000..17f930aa40 --- /dev/null +++ b/apps/document_signatures/migrations/0001_initial.py @@ -0,0 +1,140 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'DocumentVersionSignature' + db.create_table('document_signatures_documentversionsignature', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('document_version', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['documents.DocumentVersion'])), + ('signature_state', self.gf('django.db.models.fields.CharField')(max_length=16, null=True, blank=True)), + ('signature_file', self.gf('django.db.models.fields.files.FileField')(max_length=100, null=True, blank=True)), + )) + db.send_create_signal('document_signatures', ['DocumentVersionSignature']) + + + def backwards(self, orm): + + # Deleting model 'DocumentVersionSignature' + db.delete_table('document_signatures_documentversionsignature') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'comments.comment': { + 'Meta': {'ordering': "('submit_date',)", 'object_name': 'Comment', 'db_table': "'django_comments'"}, + 'comment': ('django.db.models.fields.TextField', [], {'max_length': '3000'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_type_set_for_comment'", 'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_removed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_pk': ('django.db.models.fields.TextField', [], {}), + 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}), + 'submit_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comment_comments'", 'null': 'True', 'to': "orm['auth.User']"}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'user_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'user_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'document_signatures.documentversionsignature': { + 'Meta': {'object_name': 'DocumentVersionSignature'}, + 'document_version': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.DocumentVersion']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'signature_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'signature_state': ('django.db.models.fields.CharField', [], {'max_length': '16', 'null': 'True', 'blank': 'True'}) + }, + 'documents.document': { + 'Meta': {'ordering': "['-date_added']", 'object_name': 'Document'}, + 'date_added': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'document_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.DocumentType']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '48', 'blank': 'True'}) + }, + 'documents.documenttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'DocumentType'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32'}) + }, + 'documents.documentversion': { + 'Meta': {'unique_together': "(('document', 'major', 'minor', 'micro', 'release_level', 'serial'),)", 'object_name': 'DocumentVersion'}, + 'checksum': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.Document']"}), + 'encoding': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64'}), + 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'filename': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'major': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'micro': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'mimetype': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64'}), + 'minor': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'release_level': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'serial': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'signature_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'signature_state': ('django.db.models.fields.CharField', [], {'max_length': '16', 'null': 'True', 'blank': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {}) + }, + 'sites.site': { + 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'taggit.tag': { + 'Meta': {'object_name': 'Tag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'}) + }, + 'taggit.taggeditem': { + 'Meta': {'object_name': 'TaggedItem'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"}) + } + } + + complete_apps = ['document_signatures'] diff --git a/apps/document_signatures/migrations/0002_move_signatures_to_new_app.py b/apps/document_signatures/migrations/0002_move_signatures_to_new_app.py new file mode 100644 index 0000000000..2fef743da0 --- /dev/null +++ b/apps/document_signatures/migrations/0002_move_signatures_to_new_app.py @@ -0,0 +1,145 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + for document_version in orm.DocumentVersion.objects.all(): + if document_version.signature_state or document_version.signature_file: + document_signature = orm.DocumentVersionSignature( + document_version=document_version, + signature_state=document_version.signature_state, + signature_file=document_version.signature_file, + ) + document_signature.save() + + + def backwards(self, orm): + for document_signature in orm.DocumentVersionSignature.objects.all(): + try: + document_version = orm.DocumentVersion.objects.get(document_version=document_version) + except orm.DocumentVersion.DoesNotExists: + pass + else: + document_version.signature_state=document_signature.signature_state + document_version.signature_file=document_signature.signature_file + document_version.save() + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'comments.comment': { + 'Meta': {'ordering': "('submit_date',)", 'object_name': 'Comment', 'db_table': "'django_comments'"}, + 'comment': ('django.db.models.fields.TextField', [], {'max_length': '3000'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'content_type_set_for_comment'", 'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'ip_address': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), + 'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_removed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_pk': ('django.db.models.fields.TextField', [], {}), + 'site': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['sites.Site']"}), + 'submit_date': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'comment_comments'", 'null': 'True', 'to': "orm['auth.User']"}), + 'user_email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'user_name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), + 'user_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'document_signatures.documentversionsignature': { + 'Meta': {'object_name': 'DocumentVersionSignature'}, + 'document_version': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.DocumentVersion']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'signature_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'signature_state': ('django.db.models.fields.CharField', [], {'max_length': '16', 'null': 'True', 'blank': 'True'}) + }, + 'documents.document': { + 'Meta': {'ordering': "['-date_added']", 'object_name': 'Document'}, + 'date_added': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}), + 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'document_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.DocumentType']", 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'uuid': ('django.db.models.fields.CharField', [], {'max_length': '48', 'blank': 'True'}) + }, + 'documents.documenttype': { + 'Meta': {'ordering': "['name']", 'object_name': 'DocumentType'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '32'}) + }, + 'documents.documentversion': { + 'Meta': {'unique_together': "(('document', 'major', 'minor', 'micro', 'release_level', 'serial'),)", 'object_name': 'DocumentVersion'}, + 'checksum': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}), + 'comment': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + 'document': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['documents.Document']"}), + 'encoding': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64'}), + 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), + 'filename': ('django.db.models.fields.CharField', [], {'default': "u''", 'max_length': '255', 'db_index': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'major': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'micro': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'mimetype': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64'}), + 'minor': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'release_level': ('django.db.models.fields.PositiveIntegerField', [], {'default': '1'}), + 'serial': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}), + 'signature_file': ('django.db.models.fields.files.FileField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'signature_state': ('django.db.models.fields.CharField', [], {'max_length': '16', 'null': 'True', 'blank': 'True'}), + 'timestamp': ('django.db.models.fields.DateTimeField', [], {}) + }, + 'sites.site': { + 'Meta': {'ordering': "('domain',)", 'object_name': 'Site', 'db_table': "'django_site'"}, + 'domain': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'taggit.tag': { + 'Meta': {'object_name': 'Tag'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'}) + }, + 'taggit.taggeditem': { + 'Meta': {'object_name': 'TaggedItem'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_tagged_items'", 'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'object_id': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}), + 'tag': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'taggit_taggeditem_items'", 'to': "orm['taggit.Tag']"}) + } + } + + complete_apps = ['document_signatures'] diff --git a/apps/document_signatures/migrations/__init__.py b/apps/document_signatures/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/document_signatures/models.py b/apps/document_signatures/models.py new file mode 100644 index 0000000000..0f82666b84 --- /dev/null +++ b/apps/document_signatures/models.py @@ -0,0 +1,27 @@ +from __future__ import absolute_import +import logging + +from django.db import models +from django.utils.translation import ugettext_lazy as _ + +from documents.models import DocumentVersion, get_filename_from_uuid +from documents.conf.settings import STORAGE_BACKEND + +from .managers import DocumentVersionSignatureManager + +logger = logging.getLogger(__name__) + + +class DocumentVersionSignature(models.Model): + ''' + Model that describes a document version signature properties + ''' + document_version = models.ForeignKey(DocumentVersion, verbose_name=_(u'document version'), editable=False) + signature_state = models.CharField(blank=True, null=True, max_length=16, verbose_name=_(u'signature state'), editable=False) + signature_file = models.FileField(blank=True, null=True, upload_to=get_filename_from_uuid, storage=STORAGE_BACKEND(), verbose_name=_(u'signature file'), editable=False) + + objects = DocumentVersionSignatureManager() + + class Meta: + verbose_name = _(u'document version signature') + verbose_name_plural = _(u'document version signatures') diff --git a/apps/document_signatures/permissions.py b/apps/document_signatures/permissions.py new file mode 100644 index 0000000000..9bbfdfece8 --- /dev/null +++ b/apps/document_signatures/permissions.py @@ -0,0 +1,15 @@ +from __future__ import absolute_import + +from django.utils.translation import ugettext_lazy as _ + +from permissions.api import register_permission, set_namespace_title + +PERMISSION_DOCUMENT_VERIFY = {'namespace': 'document_signatures', 'name': 'document_verify', 'label': _(u'Verify document signatures')} +PERMISSION_SIGNATURE_UPLOAD = {'namespace': 'document_signatures', 'name': 'signature_upload', 'label': _(u'Upload detached signatures')} +PERMISSION_SIGNATURE_DOWNLOAD = {'namespace': 'document_signatures', 'name': 'key_receive', 'label': _(u'Download detached signatures')} + +# Permission setup +set_namespace_title('document_signatures', _(u'Document signatures')) +register_permission(PERMISSION_DOCUMENT_VERIFY) +register_permission(PERMISSION_SIGNATURE_UPLOAD) +register_permission(PERMISSION_SIGNATURE_DOWNLOAD) diff --git a/apps/document_signatures/tests.py b/apps/document_signatures/tests.py new file mode 100644 index 0000000000..501deb776c --- /dev/null +++ b/apps/document_signatures/tests.py @@ -0,0 +1,16 @@ +""" +This file demonstrates writing tests using the unittest module. These will pass +when you run "manage.py test". + +Replace this with more appropriate tests for your application. +""" + +from django.test import TestCase + + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.assertEqual(1 + 1, 2) diff --git a/apps/document_signatures/urls.py b/apps/document_signatures/urls.py new file mode 100644 index 0000000000..37d857aa40 --- /dev/null +++ b/apps/document_signatures/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls.defaults import patterns, url + +urlpatterns = patterns('document_signatures.views', + url(r'^verify/(?P\d+)/$', 'document_verify', (), 'document_verify'), + url(r'^upload/signature/(?P\d+)/$', 'document_signature_upload', (), 'document_signature_upload'), + url(r'^download/signature/(?P\d+)/$', 'document_signature_download', (), 'document_signature_download'), +) diff --git a/apps/document_signatures/views.py b/apps/document_signatures/views.py new file mode 100644 index 0000000000..6c3d51fddf --- /dev/null +++ b/apps/document_signatures/views.py @@ -0,0 +1,121 @@ +from __future__ import absolute_import + +from datetime import datetime +import logging + +from django.utils.translation import ugettext_lazy as _ +from django.http import HttpResponseRedirect +from django.shortcuts import render_to_response, get_object_or_404 +from django.template import RequestContext +from django.contrib import messages +from django.utils.safestring import mark_safe +from django.conf import settings +from django.template.defaultfilters import force_escape + +from documents.models import Document, RecentDocument +from permissions.api import check_permissions +from filetransfers.api import serve_file + +from django_gpg.api import SIGNATURE_STATES + +from . import (PERMISSION_DOCUMENT_VERIFY, PERMISSION_SIGNATURE_UPLOAD, + PERMISSION_SIGNATURE_DOWNLOAD) +from .forms import DetachedSignatureForm +from .models import DocumentVersionSignature + +logger = logging.getLogger(__name__) + + +def document_verify(request, document_pk): + check_permissions(request.user, [PERMISSION_DOCUMENT_VERIFY]) + document = get_object_or_404(Document, pk=document_pk) + + RecentDocument.objects.add_document_for_user(request.user, document) + + signature = DocumentVersionSignature.objects.verify_signature(document) + + signature_state = SIGNATURE_STATES.get(getattr(signature, 'status', None)) + + widget = (u'' % (settings.STATIC_URL, signature_state['icon'])) + paragraphs = [ + _(u'Signature status: %(widget)s %(text)s') % { + 'widget': mark_safe(widget), + 'text': signature_state['text'] + }, + ] + + if DocumentVersionSignature.objects.has_embedded_signature(document): + signature_type = _(u'embedded') + else: + signature_type = _(u'detached') + + if signature: + paragraphs.extend( + [ + _(u'Signature ID: %s') % signature.signature_id, + _(u'Signature type: %s') % signature_type, + _(u'Key ID: %s') % signature.key_id, + _(u'Timestamp: %s') % datetime.fromtimestamp(int(signature.sig_timestamp)), + _(u'Signee: %s') % force_escape(getattr(signature, 'username', u'')), + ] + ) + + return render_to_response('generic_template.html', { + 'title': _(u'signature properties for: %s') % document, + 'object': document, + 'document': document, + 'paragraphs': paragraphs, + }, context_instance=RequestContext(request)) + + +def document_signature_upload(request, document_pk): + check_permissions(request.user, [PERMISSION_SIGNATURE_UPLOAD]) + document = get_object_or_404(Document, pk=document_pk) + + RecentDocument.objects.add_document_for_user(request.user, document) + + post_action_redirect = None + previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', '/'))) + next = request.POST.get('next', request.GET.get('next', post_action_redirect if post_action_redirect else request.META.get('HTTP_REFERER', '/'))) + + if request.method == 'POST': + form = DetachedSignatureForm(request.POST, request.FILES) + if form.is_valid(): + try: + DocumentVersionSignature.objects.add_detached_signature(document, request.FILES['file']) + messages.success(request, _(u'Detached signature uploaded successfully.')) + return HttpResponseRedirect(next) + except Exception, msg: + messages.error(request, msg) + return HttpResponseRedirect(previous) + else: + form = DetachedSignatureForm() + + return render_to_response('generic_form.html', { + 'title': _(u'Upload detached signature for: %s') % document, + 'form_icon': 'key_delete.png', + 'next': next, + 'form': form, + 'previous': previous, + 'object': document, + }, context_instance=RequestContext(request)) + + +def document_signature_download(request, document_pk): + check_permissions(request.user, [PERMISSION_SIGNATURE_DOWNLOAD]) + document = get_object_or_404(Document, pk=document_pk) + + try: + if DocumentVersionSignature.objects.has_detached_signature(document): + signature = DocumentVersionSignature.objects.detached_signature(document) + return serve_file( + request, + signature, + save_as=u'"%s.sig"' % document.filename, + content_type=u'application/octet-stream' + ) + except Exception, e: + messages.error(request, e) + return HttpResponseRedirect(request.META['HTTP_REFERER']) + + return HttpResponseRedirect(request.META['HTTP_REFERER']) diff --git a/apps/documents/models.py b/apps/documents/models.py index d05737632a..2255d487fe 100644 --- a/apps/documents/models.py +++ b/apps/documents/models.py @@ -15,11 +15,8 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext from django.contrib.auth.models import User -from django.contrib.contenttypes import generic -from django.contrib.comments.models import Comment from django.core.exceptions import ValidationError -from taggit.managers import TaggableManager from dynamic_search.api import register from converter.api import get_page_count from converter.api import get_available_transformations_choices @@ -29,8 +26,6 @@ from mimetype.api import (get_mimetype, get_icon_file_path, get_error_icon_file_path) from converter.literals import (DEFAULT_ZOOM_LEVEL, DEFAULT_ROTATION, DEFAULT_PAGE_NUMBER) -from django_gpg.runtime import gpg -from django_gpg.exceptions import GPGVerificationError, GPGDecryptionError from documents.conf.settings import CHECKSUM_FUNCTION from documents.conf.settings import UUID_FUNCTION @@ -86,14 +81,6 @@ class Document(models.Model): description = models.TextField(blank=True, null=True, verbose_name=_(u'description')) date_added = models.DateTimeField(verbose_name=_(u'added'), db_index=True, editable=False) - tags = TaggableManager() - - comments = generic.GenericRelation( - Comment, - content_type_field='content_type', - object_id_field='object_pk' - ) - @staticmethod def clear_image_cache(): for the_file in os.listdir(CACHE_PATH): @@ -288,7 +275,9 @@ class Document(models.Model): return version.save() filename = property(_get_filename, _set_filename) - + + #TODO: remove after migration + """ def add_detached_signature(self, *args, **kwargs): return self.latest_version.add_detached_signature(*args, **kwargs) @@ -300,12 +289,14 @@ class Document(models.Model): def verify_signature(self): return self.latest_version.verify_signature() - + """ class DocumentVersion(models.Model): ''' Model that describes a document version and its properties ''' + _pre_open_hooks = {} + @staticmethod def get_version_update_choices(document_version): return ( @@ -314,6 +305,10 @@ class DocumentVersion(models.Model): (VERSION_UPDATE_MICRO, _(u'Micro %(major)i.%(minor)i.%(micro)i, (fixes)') % document_version.get_new_version_dict(VERSION_UPDATE_MICRO)) ) + @classmethod + def register_pre_open_hook(cls, order, func): + cls._pre_open_hooks[order] = func + document = models.ForeignKey(Document, verbose_name=_(u'document'), editable=False) major = models.PositiveIntegerField(verbose_name=_(u'mayor'), default=1, editable=False) minor = models.PositiveIntegerField(verbose_name=_(u'minor'), default=0, editable=False) @@ -329,6 +324,8 @@ class DocumentVersion(models.Model): encoding = models.CharField(max_length=64, default='', editable=False) filename = models.CharField(max_length=255, default=u'', editable=False, db_index=True) checksum = models.TextField(blank=True, null=True, verbose_name=_(u'checksum'), editable=False) + + #TODO: to be removed after migration signature_state = models.CharField(blank=True, null=True, max_length=16, verbose_name=_(u'signature state'), editable=False) signature_file = models.FileField(blank=True, null=True, upload_to=get_filename_from_uuid, storage=STORAGE_BACKEND(), verbose_name=_(u'signature file'), editable=False) @@ -393,7 +390,9 @@ class DocumentVersion(models.Model): if new_document: #Only do this for new documents - self.update_signed_state(save=False) + #Only do this for new documents + # TODO: remove after migration + #self.update_signed_state(save=False) self.update_checksum(save=False) self.update_mimetype(save=False) self.save() @@ -467,6 +466,8 @@ class DocumentVersion(models.Model): for version in self.document.versions.filter(timestamp__gt=self.timestamp): version.delete() + #TODO: remove after migration + """ def update_signed_state(self, save=True): if self.exists(): try: @@ -478,7 +479,8 @@ class DocumentVersion(models.Model): if save: self.save() - + """ + def update_mimetype(self, save=True): ''' Read a document verions's file and determine the mimetype by calling the @@ -510,6 +512,16 @@ class DocumentVersion(models.Model): Return a file descriptor to a document version's file irrespective of the storage backend ''' + if raw: + return self.file.storage.open(self.file.path) + else: + result = self.file.storage.open(self.file.path) + for key in sorted(DocumentVersion._pre_open_hooks): + result = DocumentVersion._pre_open_hooks[key](result) + + return result + #TODO: remove after migration + """ if self.signature_state and not raw: try: result = gpg.decrypt_file(self.file.storage.open(self.file.path)) @@ -520,6 +532,7 @@ class DocumentVersion(models.Model): return self.file.storage.open(self.file.path) else: return self.file.storage.open(self.file.path) + """ def save_to_file(self, filepath, buffer_size=1024 * 1024): ''' @@ -545,7 +558,8 @@ class DocumentVersion(models.Model): return self.file.storage.size(self.file.path) else: return None - + #TODO: remove after migration + """ def add_detached_signature(self, detached_signature): if not self.signature_state: self.signature_file = detached_signature @@ -573,6 +587,7 @@ class DocumentVersion(models.Model): signature = None return signature + """ class DocumentTypeFilename(models.Model): diff --git a/apps/documents/tests.py b/apps/documents/tests.py index 2247054b35..72da245be4 100644 --- a/apps/documents/tests.py +++ b/apps/documents/tests.py @@ -1,23 +1,85 @@ -""" -This file demonstrates two different styles of tests (one doctest and one -unittest). These will both pass when you run "manage.py test". +import os -Replace these with more appropriate tests for your application. -""" +from django.utils import unittest +from django.test.client import Client +from django.conf import settings +from django.core.files.base import File -from django.test import TestCase +from django_gpg.api import SIGNATURE_STATE_VALID -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. - """ - self.failUnlessEqual(1 + 1, 2) +from .models import Document, DocumentType +from .literals import VERSION_UPDATE_MAJOR, RELEASE_LEVEL_FINAL -__test__ = {"doctest": """ -Another way to test that 1 + 1 is equal to 2. ->>> 1 + 1 == 2 -True -"""} +class DocumentTestCase(unittest.TestCase): + def setUp(self): + self.document_type = DocumentType(name='test doc type') + self.document_type.save() + self.document = Document( + document_type = self.document_type, + description = 'description', + ) + self.document.save() + #return File(file(self.filepath, 'rb'), name=self.filename) + + file_object = open(os.path.join(settings.PROJECT_ROOT, 'contrib', 'mayan_11_1.pdf')) + new_version = self.document.new_version(file=File(file_object, name='mayan_11_1.pdf')) + file_object.close() + + def runTest(self): + self.failUnlessEqual(self.document_type.name, 'test doc type') + + self.failUnlessEqual(self.document.exists(), True) + self.failUnlessEqual(self.document.size, 272213) + + self.failUnlessEqual(self.document.file_mimetype, 'application/pdf') + self.failUnlessEqual(self.document.file_mime_encoding, 'binary') + self.failUnlessEqual(self.document.file_filename, 'mayan_11_1.pdf') + self.failUnlessEqual(self.document.checksum, 'c637ffab6b8bb026ed3784afdb07663fddc60099853fae2be93890852a69ecf3') + self.failUnlessEqual(self.document.page_count, 47) + + self.failUnlessEqual(self.document.latest_version.get_formated_version(), '1.0') + self.failUnlessEqual(self.document.has_detached_signature(), False) + + file_object = open(os.path.join(settings.PROJECT_ROOT, 'contrib', 'mayan_11_1.pdf.gpg')) + new_version_data = { + 'comment': 'test comment 1', + 'version_update': VERSION_UPDATE_MAJOR, + 'release_level': RELEASE_LEVEL_FINAL, + 'serial': 0, + } + + new_version = self.document.new_version(file=File(file_object, name='mayan_11_1.pdf.gpg'), **new_version_data) + file_object.close() + + self.failUnlessEqual(self.document.latest_version.get_formated_version(), '2.0') + self.failUnlessEqual(self.document.has_detached_signature(), False) + + self.failUnlessEqual(self.document.verify_signature().status, SIGNATURE_STATE_VALID) + + new_version_data = { + 'comment': 'test comment 2', + 'version_update': VERSION_UPDATE_MAJOR, + 'release_level': RELEASE_LEVEL_FINAL, + 'serial': 0, + } + file_object = open(os.path.join(settings.PROJECT_ROOT, 'contrib', 'mayan_11_1.pdf')) + new_version = self.document.new_version(file=File(file_object), **new_version_data) + file_object.close() + + self.failUnlessEqual(self.document.latest_version.get_formated_version(), '3.0') + + #GPGVerificationError + self.failUnlessEqual(self.document.verify_signature(), None) + + file_object = open(os.path.join(settings.PROJECT_ROOT, 'contrib', 'mayan_11_1.pdf.sig'), 'rb') + new_version = self.document.add_detached_signature(File(file_object)) + file_object.close() + + self.failUnlessEqual(self.document.has_detached_signature(), True) + self.failUnlessEqual(self.document.verify_signature().status, SIGNATURE_STATE_VALID) + + + def tearDown(self): + self.document.delete() diff --git a/apps/ocr/api.py b/apps/ocr/api.py index e568ecf6c8..958ff864eb 100644 --- a/apps/ocr/api.py +++ b/apps/ocr/api.py @@ -96,7 +96,7 @@ def do_document_ocr(queue_document): # Fall back to doing visual OCR ocr_transformations, warnings = queue_document.get_transformation_list() - document_filepath = document_page.document.get_image_cache_name(page=document_page.page_number) + document_filepath = document_page.document.get_image_cache_name(page=document_page.page_number, version=document_page.document_version.pk) unpaper_output_filename = u'%s_unpaper_out_page_%s%s%s' % (document_page.document.uuid, document_page.page_number, os.extsep, UNPAPER_FILE_FORMAT) unpaper_output_filepath = os.path.join(TEMPORARY_DIRECTORY, unpaper_output_filename) diff --git a/apps/sources/views.py b/apps/sources/views.py index 1dcce811c4..b0701cfece 100644 --- a/apps/sources/views.py +++ b/apps/sources/views.py @@ -6,6 +6,7 @@ from django.core.urlresolvers import reverse from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext from django.utils.safestring import mark_safe +from django.conf import settings from documents.literals import PERMISSION_DOCUMENT_CREATE from documents.models import DocumentType, Document @@ -161,6 +162,8 @@ def upload_interactive(request, source_type=None, source_id=None, document_pk=No messages.success(request, _(u'Document uploaded successfully.')) return HttpResponseRedirect(request.get_full_path()) except Exception, e: + if settings.DEBUG: + raise messages.error(request, _(u'Unhandled exception: %s') % e) else: form = WebFormForm( @@ -231,6 +234,8 @@ def upload_interactive(request, source_type=None, source_id=None, document_pk=No else: return HttpResponseRedirect(request.get_full_path()) except Exception, e: + if settings.DEBUG: + raise messages.error(request, _(u'Unhandled exception: %s') % e) else: form = StagingDocumentForm(cls=StagingFile, diff --git a/apps/tags/__init__.py b/apps/tags/__init__.py index 07f68dac63..f4f0adad7d 100644 --- a/apps/tags/__init__.py +++ b/apps/tags/__init__.py @@ -8,6 +8,7 @@ from documents.models import Document from acls.models import class_permissions from taggit.models import Tag +from taggit.managers import TaggableManager from tags.widgets import tag_color_block @@ -67,3 +68,5 @@ class_permissions(Document, [ # PERMISSION_TAG_EDIT, # PERMISSION_TAG_VIEW, #]) + +Document.add_to_class('tags', TaggableManager()) diff --git a/apps/tags/tests.py b/apps/tags/tests.py index 2247054b35..612cd1803e 100644 --- a/apps/tags/tests.py +++ b/apps/tags/tests.py @@ -1,23 +1,15 @@ -""" -This file demonstrates two different styles of tests (one doctest and one -unittest). These will both pass when you run "manage.py test". +from django.utils import unittest -Replace these with more appropriate tests for your application. -""" +from .models import Tag, TagProperties, COLOR_RED -from django.test import TestCase -class SimpleTest(TestCase): - def test_basic_addition(self): - """ - Tests that 1 + 1 always equals 2. - """ - self.failUnlessEqual(1 + 1, 2) - -__test__ = {"doctest": """ -Another way to test that 1 + 1 is equal to 2. - ->>> 1 + 1 == 2 -True -"""} +class TagTestCase(unittest.TestCase): + def setUp(self): + self.tag = Tag(name='test') + self.tag.save() + self.tp = TagProperties(tag=self.tag, color=COLOR_RED) + self.tp.save() + def runTest(self): + self.failUnlessEqual(self.tag.name, 'test') + self.failUnlessEqual(self.tp.get_color_code(), 'red') diff --git a/contrib/mayan_11_1.pdf b/contrib/mayan_11_1.pdf new file mode 100644 index 0000000000..5fba2c87c3 Binary files /dev/null and b/contrib/mayan_11_1.pdf differ diff --git a/contrib/mayan_11_1.pdf.gpg b/contrib/mayan_11_1.pdf.gpg new file mode 100644 index 0000000000..e271fe8df3 Binary files /dev/null and b/contrib/mayan_11_1.pdf.gpg differ diff --git a/contrib/mayan_11_1.pdf.sig b/contrib/mayan_11_1.pdf.sig new file mode 100644 index 0000000000..de11aa6db1 Binary files /dev/null and b/contrib/mayan_11_1.pdf.sig differ diff --git a/misc/compilemessages_all.sh b/misc/compilemessages_all.sh index a6abd085c7..b37e263fac 100755 --- a/misc/compilemessages_all.sh +++ b/misc/compilemessages_all.sh @@ -134,3 +134,9 @@ $COMPILEMESSAGES -l pt $COMPILEMESSAGES -l ru $COMPILEMESSAGES -l es $COMPILEMESSAGES -l it + +cd $BASE/apps/document_signatures +$COMPILEMESSAGES -l pt +$COMPILEMESSAGES -l ru +$COMPILEMESSAGES -l es +$COMPILEMESSAGES -l it diff --git a/misc/makemessages_all.sh b/misc/makemessages_all.sh index 3cc25f2a01..beba6e7d53 100755 --- a/misc/makemessages_all.sh +++ b/misc/makemessages_all.sh @@ -156,3 +156,10 @@ $MAKEMESSAGES -l pt $MAKEMESSAGES -l ru $MAKEMESSAGES -l es $MAKEMESSAGES -l it + +cd $BASE/apps/document_signatures +$MAKEMESSAGES -l en +$MAKEMESSAGES -l pt +$MAKEMESSAGES -l ru +$MAKEMESSAGES -l es +$MAKEMESSAGES -l it diff --git a/settings.py b/settings.py index 2ff585af7e..9a64a7ce74 100644 --- a/settings.py +++ b/settings.py @@ -170,6 +170,7 @@ INSTALLED_APPS = ( 'djangorestframework', 'rest_api', 'south', + 'document_signatures', ) TEMPLATE_CONTEXT_PROCESSORS = ( diff --git a/urls.py b/urls.py index 84690edb87..4f88bbff7d 100644 --- a/urls.py +++ b/urls.py @@ -30,7 +30,8 @@ urlpatterns = patterns('', (r'^acls/', include('acls.urls')), (r'^document_acls/', include('document_acls.urls')), (r'^api/', include('rest_api.urls')), - (r'^signatures/', include('django_gpg.urls')), + (r'^gpg/', include('django_gpg.urls')), + (r'^documents/signatures/', include('document_signatures.urls')), )