Merge branch 'feature/document_signature_app' into feature/ACL_support_w_permission_refactor

Conflicts:
	apps/django_gpg/__init__.py
	apps/django_gpg/views.py
	apps/tags/__init__.py
This commit is contained in:
Roberto Rosario
2012-01-01 20:50:28 -04:00
36 changed files with 1457 additions and 228 deletions

View File

@@ -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

View File

@@ -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')

View File

@@ -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')

View File

@@ -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'),
)

View File

@@ -4,9 +4,6 @@ urlpatterns = patterns('django_gpg.views',
url(r'^delete/(?P<fingerprint>.+)/(?P<key_type>\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<document_pk>\d+)/$', 'document_verify', (), 'document_verify'),
url(r'^upload/signature/(?P<document_pk>\d+)/$', 'document_signature_upload', (), 'document_signature_upload'),
url(r'^download/signature/(?P<document_pk>\d+)/$', 'document_signature_download', (), 'document_signature_download'),
url(r'^query/$', 'key_query', (), 'key_query'),
url(r'^receive/(?P<key_id>.+)/$', 'key_receive', (), 'key_receive'),
)

View File

@@ -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'<img style="vertical-align: middle;" src="%simages/icons/%s" />' % (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'])

View File

@@ -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'
)
)

View File

@@ -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)

View File

@@ -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'),
)

View File

@@ -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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@@ -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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@@ -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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@@ -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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@@ -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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@@ -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

View File

@@ -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']

View File

@@ -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']

View File

@@ -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')

View File

@@ -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)

View File

@@ -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)

View File

@@ -0,0 +1,7 @@
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('document_signatures.views',
url(r'^verify/(?P<document_pk>\d+)/$', 'document_verify', (), 'document_verify'),
url(r'^upload/signature/(?P<document_pk>\d+)/$', 'document_signature_upload', (), 'document_signature_upload'),
url(r'^download/signature/(?P<document_pk>\d+)/$', 'document_signature_download', (), 'document_signature_download'),
)

View File

@@ -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'<img style="vertical-align: middle;" src="%simages/icons/%s" />' % (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'])

View File

@@ -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):

View File

@@ -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()

View File

@@ -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)

View File

@@ -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,

View File

@@ -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())

View File

@@ -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')

BIN
contrib/mayan_11_1.pdf Normal file

Binary file not shown.

BIN
contrib/mayan_11_1.pdf.gpg Normal file

Binary file not shown.

BIN
contrib/mayan_11_1.pdf.sig Normal file

Binary file not shown.

View File

@@ -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

View File

@@ -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

View File

@@ -170,6 +170,7 @@ INSTALLED_APPS = (
'djangorestframework',
'rest_api',
'south',
'document_signatures',
)
TEMPLATE_CONTEXT_PROCESSORS = (

View File

@@ -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')),
)