PEP8 cleanups, E501.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
from json import dumps, loads
|
from json import dumps
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
|||||||
@@ -56,7 +56,10 @@ class Python(ConverterBase):
|
|||||||
|
|
||||||
image_buffer = io.BytesIO()
|
image_buffer = io.BytesIO()
|
||||||
try:
|
try:
|
||||||
pdftoppm(input_filepath, f=self.page_number + 1, l=self.page_number + 1, _out=image_buffer)
|
pdftoppm(
|
||||||
|
input_filepath, f=self.page_number + 1,
|
||||||
|
l=self.page_number + 1, _out=image_buffer
|
||||||
|
)
|
||||||
image_buffer.seek(0)
|
image_buffer.seek(0)
|
||||||
return Image.open(image_buffer)
|
return Image.open(image_buffer)
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ class TransformationManager(models.Manager):
|
|||||||
)
|
)
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
logger.error(
|
logger.error(
|
||||||
'Error while parsing transformation "%s", arguments "%s", for object "%s"; %s',
|
'Error while parsing transformation "%s", '
|
||||||
|
'arguments "%s", for object "%s"; %s',
|
||||||
transformation, transformation.arguments, obj,
|
transformation, transformation.arguments, obj,
|
||||||
exception
|
exception
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -96,7 +96,9 @@ class Key(object):
|
|||||||
return ', '.join(self.uids)
|
return ', '.join(self.uids)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s "%s" (%s)' % (self.key_id, self.user_ids, KEY_TYPES.get(self.type, _('Unknown')))
|
return '%s "%s" (%s)' % (
|
||||||
|
self.key_id, self.user_ids, KEY_TYPES.get(self.type, _('Unknown'))
|
||||||
|
)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return unicode(self.__str__())
|
return unicode(self.__str__())
|
||||||
@@ -133,9 +135,13 @@ class GPG(object):
|
|||||||
try:
|
try:
|
||||||
self.gpg = gnupg.GPG(**kwargs)
|
self.gpg = gnupg.GPG(**kwargs)
|
||||||
except OSError as exception:
|
except OSError as exception:
|
||||||
raise GPGException('ERROR: GPG initialization error; Make sure the GPG binary is properly installed; %s' % exception)
|
raise GPGException(
|
||||||
|
'ERROR: GPG initialization error; Make sure the GPG binary is properly installed; %s' % exception
|
||||||
|
)
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
raise GPGException('ERROR: GPG initialization error; %s' % exception)
|
raise GPGException(
|
||||||
|
'ERROR: GPG initialization error; %s' % exception
|
||||||
|
)
|
||||||
|
|
||||||
def verify_file(self, file_input, detached_signature=None, fetch_key=False):
|
def verify_file(self, file_input, detached_signature=None, fetch_key=False):
|
||||||
"""
|
"""
|
||||||
@@ -155,7 +161,9 @@ class GPG(object):
|
|||||||
signature_file = StringIO()
|
signature_file = StringIO()
|
||||||
signature_file.write(detached_signature.read())
|
signature_file.write(detached_signature.read())
|
||||||
signature_file.seek(0)
|
signature_file.seek(0)
|
||||||
verify = self.gpg.verify_file(signature_file, data_filename=filename)
|
verify = self.gpg.verify_file(
|
||||||
|
signature_file, data_filename=filename
|
||||||
|
)
|
||||||
signature_file.close()
|
signature_file.close()
|
||||||
else:
|
else:
|
||||||
verify = self.gpg.verify_file(input_descriptor)
|
verify = self.gpg.verify_file(input_descriptor)
|
||||||
@@ -169,7 +177,9 @@ class GPG(object):
|
|||||||
if fetch_key:
|
if fetch_key:
|
||||||
try:
|
try:
|
||||||
self.receive_key(verify.key_id)
|
self.receive_key(verify.key_id)
|
||||||
return self.verify_file(input_descriptor, detached_signature, fetch_key=False)
|
return self.verify_file(
|
||||||
|
input_descriptor, detached_signature, fetch_key=False
|
||||||
|
)
|
||||||
except KeyFetchingError:
|
except KeyFetchingError:
|
||||||
return verify
|
return verify
|
||||||
else:
|
else:
|
||||||
@@ -259,7 +269,9 @@ class GPG(object):
|
|||||||
return Key.get(self, key.fingerprint)
|
return Key.get(self, key.fingerprint)
|
||||||
|
|
||||||
def delete_key(self, key):
|
def delete_key(self, key):
|
||||||
status = self.gpg.delete_keys(key.fingerprint, key.type == 'sec').status
|
status = self.gpg.delete_keys(
|
||||||
|
key.fingerprint, key.type == 'sec'
|
||||||
|
).status
|
||||||
if status == 'Must delete secret key first':
|
if status == 'Must delete secret key first':
|
||||||
self.delete_key(Key.get(self, key.fingerprint, secret=True))
|
self.delete_key(Key.get(self, key.fingerprint, secret=True))
|
||||||
self.delete_key(key)
|
self.delete_key(key)
|
||||||
@@ -270,7 +282,9 @@ class GPG(object):
|
|||||||
for keyserver in self.keyservers:
|
for keyserver in self.keyservers:
|
||||||
import_result = self.gpg.recv_keys(keyserver, key_id)
|
import_result = self.gpg.recv_keys(keyserver, key_id)
|
||||||
if import_result:
|
if import_result:
|
||||||
return Key.get(self, import_result.fingerprints[0], secret=False)
|
return Key.get(
|
||||||
|
self, import_result.fingerprints[0], secret=False
|
||||||
|
)
|
||||||
|
|
||||||
raise KeyFetchingError
|
raise KeyFetchingError
|
||||||
|
|
||||||
|
|||||||
@@ -22,4 +22,10 @@ class DjangoGPGApp(MayanAppConfig):
|
|||||||
menu_object.bind_links(links=[link_key_delete], sources=[Key])
|
menu_object.bind_links(links=[link_key_delete], sources=[Key])
|
||||||
menu_object.bind_links(links=[link_key_receive], sources=[KeyStub])
|
menu_object.bind_links(links=[link_key_receive], sources=[KeyStub])
|
||||||
menu_setup.bind_links(links=[link_key_setup])
|
menu_setup.bind_links(links=[link_key_setup])
|
||||||
menu_sidebar.bind_links(links=[link_public_keys, link_key_query], sources=['django_gpg:key_delete', 'django_gpg:key_public_list', 'django_gpg:key_query'])
|
menu_sidebar.bind_links(
|
||||||
|
links=[link_public_keys, link_key_query],
|
||||||
|
sources=[
|
||||||
|
'django_gpg:key_delete', 'django_gpg:key_public_list',
|
||||||
|
'django_gpg:key_query'
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|||||||
@@ -9,9 +9,28 @@ from .permissions import (
|
|||||||
permission_keyserver_query
|
permission_keyserver_query
|
||||||
)
|
)
|
||||||
|
|
||||||
link_private_keys = Link(icon='fa fa-key', permissions=[permission_key_view], text=_('Private keys'), view='django_gpg:key_private_list')
|
link_private_keys = Link(
|
||||||
link_public_keys = Link(icon='fa fa-key', permissions=[permission_key_view], text=_('Public keys'), view='django_gpg:key_public_list')
|
icon='fa fa-key', permissions=[permission_key_view],
|
||||||
link_key_delete = Link(permissions=[permission_key_delete], tags='dangerous', text=_('Delete'), view='django_gpg:key_delete', args=['object.fingerprint', 'object.type'])
|
text=_('Private keys'), view='django_gpg:key_private_list'
|
||||||
link_key_query = Link(permissions=[permission_keyserver_query], text=_('Query keyservers'), view='django_gpg:key_query')
|
)
|
||||||
link_key_receive = Link(keep_query=True, permissions=[permission_key_receive], text=_('Import'), view='django_gpg:key_receive', args='object.key_id')
|
link_public_keys = Link(
|
||||||
link_key_setup = Link(icon='fa fa-key', permissions=[permission_key_view], text=_('Key management'), view='django_gpg:key_public_list')
|
icon='fa fa-key', permissions=[permission_key_view], text=_('Public keys'),
|
||||||
|
view='django_gpg:key_public_list'
|
||||||
|
)
|
||||||
|
link_key_delete = Link(
|
||||||
|
permissions=[permission_key_delete], tags='dangerous', text=_('Delete'),
|
||||||
|
view='django_gpg:key_delete', args=['object.fingerprint', 'object.type']
|
||||||
|
)
|
||||||
|
|
||||||
|
link_key_query = Link(
|
||||||
|
permissions=[permission_keyserver_query], text=_('Query keyservers'),
|
||||||
|
view='django_gpg:key_query'
|
||||||
|
)
|
||||||
|
link_key_receive = Link(
|
||||||
|
keep_query=True, permissions=[permission_key_receive], text=_('Import'),
|
||||||
|
view='django_gpg:key_receive', args='object.key_id'
|
||||||
|
)
|
||||||
|
link_key_setup = Link(
|
||||||
|
icon='fa fa-key', permissions=[permission_key_view],
|
||||||
|
text=_('Key management'), view='django_gpg:key_public_list'
|
||||||
|
)
|
||||||
|
|||||||
@@ -41,7 +41,10 @@ SIGNATURE_STATES = {
|
|||||||
'text': _('Signature error.'),
|
'text': _('Signature error.'),
|
||||||
},
|
},
|
||||||
SIGNATURE_STATE_NO_PUBLIC_KEY: {
|
SIGNATURE_STATE_NO_PUBLIC_KEY: {
|
||||||
'text': _('Document is signed but no public key is available for verification.'),
|
'text': _(
|
||||||
|
'Document is signed but no public key is available for '
|
||||||
|
'verification.'
|
||||||
|
),
|
||||||
},
|
},
|
||||||
SIGNATURE_STATE_GOOD: {
|
SIGNATURE_STATE_GOOD: {
|
||||||
'text': _('Document is signed, and signature is good.'),
|
'text': _('Document is signed, and signature is good.'),
|
||||||
|
|||||||
@@ -6,7 +6,15 @@ from permissions import PermissionNamespace
|
|||||||
|
|
||||||
namespace = PermissionNamespace('django_gpg', _('Key management'))
|
namespace = PermissionNamespace('django_gpg', _('Key management'))
|
||||||
|
|
||||||
permission_key_view = namespace.add_permission(name='key_view', label=_('View keys'))
|
permission_key_view = namespace.add_permission(
|
||||||
permission_key_delete = namespace.add_permission(name='key_delete', label=_('Delete keys'))
|
name='key_view', label=_('View keys')
|
||||||
permission_keyserver_query = namespace.add_permission(name='keyserver_query', label=_('Query keyservers'))
|
)
|
||||||
permission_key_receive = namespace.add_permission(name='key_receive', label=_('Import keys from keyservers'))
|
permission_key_delete = namespace.add_permission(
|
||||||
|
name='key_delete', label=_('Delete keys')
|
||||||
|
)
|
||||||
|
permission_keyserver_query = namespace.add_permission(
|
||||||
|
name='keyserver_query', label=_('Query keyservers')
|
||||||
|
)
|
||||||
|
permission_key_receive = namespace.add_permission(
|
||||||
|
name='key_receive', label=_('Import keys from keyservers')
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
from .api import GPG
|
from .api import GPG
|
||||||
from .settings import setting_gpg_home, setting_gpg_path, setting_keyservers
|
from .settings import setting_gpg_home, setting_gpg_path, setting_keyservers
|
||||||
|
|
||||||
gpg = GPG(binary_path=setting_gpg_path.value, home=setting_gpg_home.value, keyservers=setting_keyservers.value)
|
gpg = GPG(
|
||||||
|
binary_path=setting_gpg_path.value, home=setting_gpg_home.value,
|
||||||
|
keyservers=setting_keyservers.value
|
||||||
|
)
|
||||||
|
|||||||
@@ -8,6 +8,19 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
from smart_settings import Namespace
|
from smart_settings import Namespace
|
||||||
|
|
||||||
namespace = Namespace(name='django_gpg', label=_('Signatures'))
|
namespace = Namespace(name='django_gpg', label=_('Signatures'))
|
||||||
setting_keyservers = namespace.add_setting(global_name='SIGNATURES_KEYSERVERS', default=['pool.sks-keyservers.net'], help_text=_('List of keyservers to be queried for unknown keys.'))
|
setting_keyservers = namespace.add_setting(
|
||||||
setting_gpg_home = namespace.add_setting(global_name='SIGNATURES_GPG_HOME', default=os.path.join(settings.MEDIA_ROOT, 'gpg_home'), help_text=_('Home directory used to store keys as well as configuration files.'), is_path=True)
|
global_name='SIGNATURES_KEYSERVERS', default=['pool.sks-keyservers.net'],
|
||||||
setting_gpg_path = namespace.add_setting(global_name='SIGNATURES_GPG_PATH', default='/usr/bin/gpg', help_text=_('Path to the GPG binary.'), is_path=True)
|
help_text=_('List of keyservers to be queried for unknown keys.')
|
||||||
|
)
|
||||||
|
setting_gpg_home = namespace.add_setting(
|
||||||
|
global_name='SIGNATURES_GPG_HOME',
|
||||||
|
default=os.path.join(settings.MEDIA_ROOT, 'gpg_home'),
|
||||||
|
help_text=_(
|
||||||
|
'Home directory used to store keys as well as configuration files.'
|
||||||
|
),
|
||||||
|
is_path=True
|
||||||
|
)
|
||||||
|
setting_gpg_path = namespace.add_setting(
|
||||||
|
global_name='SIGNATURES_GPG_PATH', default='/usr/bin/gpg',
|
||||||
|
help_text=_('Path to the GPG binary.'), is_path=True
|
||||||
|
)
|
||||||
|
|||||||
@@ -20,7 +20,10 @@ class DjangoGPGTestCase(TestCase):
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.gpg = GPG(binary_path=setting_gpg_path.value, home=TEST_GPG_HOME, keyservers=TEST_KEYSERVERS)
|
self.gpg = GPG(
|
||||||
|
binary_path=setting_gpg_path.value, home=TEST_GPG_HOME,
|
||||||
|
keyservers=TEST_KEYSERVERS
|
||||||
|
)
|
||||||
|
|
||||||
def test_main(self):
|
def test_main(self):
|
||||||
# No private or public keys in the keyring
|
# No private or public keys in the keyring
|
||||||
@@ -28,10 +31,14 @@ class DjangoGPGTestCase(TestCase):
|
|||||||
self.assertEqual(Key.get_all(self.gpg), [])
|
self.assertEqual(Key.get_all(self.gpg), [])
|
||||||
|
|
||||||
# Test querying the keyservers
|
# Test querying the keyservers
|
||||||
self.assertTrue(TEST_KEY_ID in [key_stub.key_id for key_stub in self.gpg.query(TEST_UIDS)])
|
self.assertTrue(
|
||||||
|
TEST_KEY_ID in [key_stub.key_id for key_stub in self.gpg.query(TEST_UIDS)]
|
||||||
|
)
|
||||||
|
|
||||||
# Receive a public key from the keyserver
|
# Receive a public key from the keyserver
|
||||||
self.gpg.receive_key(key_id=TEST_KEY_ID[-8:])
|
self.gpg.receive_key(key_id=TEST_KEY_ID[-8:])
|
||||||
|
|
||||||
# Check that the received key is indeed in the keyring
|
# Check that the received key is indeed in the keyring
|
||||||
self.assertTrue(TEST_KEY_ID[-16:] in [key_stub.key_id for key_stub in Key.get_all(self.gpg)])
|
self.assertTrue(
|
||||||
|
TEST_KEY_ID[-16:] in [key_stub.key_id for key_stub in Key.get_all(self.gpg)]
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ from django.conf.urls import patterns, url
|
|||||||
|
|
||||||
urlpatterns = patterns(
|
urlpatterns = patterns(
|
||||||
'django_gpg.views',
|
'django_gpg.views',
|
||||||
url(r'^delete/(?P<fingerprint>.+)/(?P<key_type>\w+)/$', 'key_delete', name='key_delete'),
|
url(
|
||||||
|
r'^delete/(?P<fingerprint>.+)/(?P<key_type>\w+)/$', 'key_delete',
|
||||||
|
name='key_delete'
|
||||||
|
),
|
||||||
url(r'^list/private/$', 'key_list', {'secret': True}, 'key_private_list'),
|
url(r'^list/private/$', 'key_list', {'secret': True}, 'key_private_list'),
|
||||||
url(r'^list/public/$', 'key_list', {'secret': False}, 'key_public_list'),
|
url(r'^list/public/$', 'key_list', {'secret': False}, 'key_public_list'),
|
||||||
url(r'^query/$', 'key_query', name='key_query'),
|
url(r'^query/$', 'key_query', name='key_query'),
|
||||||
|
|||||||
@@ -112,7 +112,10 @@ def key_delete(request, fingerprint, key_type):
|
|||||||
return render_to_response('appearance/generic_confirm.html', {
|
return render_to_response('appearance/generic_confirm.html', {
|
||||||
'title': _('Delete key'),
|
'title': _('Delete key'),
|
||||||
'delete_view': True,
|
'delete_view': True,
|
||||||
'message': _('Delete key %s? If you delete a public key that is part of a public/private pair the private key will be deleted as well.') % key,
|
'message': _(
|
||||||
|
'Delete key %s? If you delete a public key that is part of a '
|
||||||
|
'public/private pair the private key will be deleted as well.'
|
||||||
|
) % key,
|
||||||
'next': next,
|
'next': next,
|
||||||
'previous': previous,
|
'previous': previous,
|
||||||
}, context_instance=RequestContext(request))
|
}, context_instance=RequestContext(request))
|
||||||
@@ -148,7 +151,9 @@ def key_query(request):
|
|||||||
'extra_columns': [
|
'extra_columns': [
|
||||||
{
|
{
|
||||||
'name': _('ID'),
|
'name': _('ID'),
|
||||||
'attribute': encapsulate(lambda item: '...{0}'.format(item.key_id[-16:])),
|
'attribute': encapsulate(
|
||||||
|
lambda item: '...{0}'.format(item.key_id[-16:])
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': _('Type'),
|
'name': _('Type'),
|
||||||
@@ -156,11 +161,15 @@ def key_query(request):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': _('Creation date'),
|
'name': _('Creation date'),
|
||||||
'attribute': encapsulate(lambda x: datetime.fromtimestamp(int(x.date)))
|
'attribute': encapsulate(
|
||||||
|
lambda x: datetime.fromtimestamp(int(x.date))
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': _('Expiration date'),
|
'name': _('Expiration date'),
|
||||||
'attribute': encapsulate(lambda x: datetime.fromtimestamp(int(x.expires)) if x.expires else _('No expiration'))
|
'attribute': encapsulate(
|
||||||
|
lambda x: datetime.fromtimestamp(int(x.expires)) if x.expires else _('No expiration')
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': _('Length'),
|
'name': _('Length'),
|
||||||
@@ -168,7 +177,9 @@ def key_query(request):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': _('Identities'),
|
'name': _('Identities'),
|
||||||
'attribute': encapsulate(lambda x: ', '.join(x.uids)),
|
'attribute': encapsulate(
|
||||||
|
lambda x: ', '.join(x.uids)
|
||||||
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -38,10 +38,15 @@ class DocumentCommentsApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
ModelAttribute(Document, label=_('Comments'), name='comments', type_name='related')
|
ModelAttribute(
|
||||||
|
Document, label=_('Comments'), name='comments', type_name='related'
|
||||||
|
)
|
||||||
|
|
||||||
SourceColumn(source=Comment, label=_('Date'), attribute='submit_date')
|
SourceColumn(source=Comment, label=_('Date'), attribute='submit_date')
|
||||||
SourceColumn(source=Comment, label=_('User'), attribute=encapsulate(lambda x: x.user.get_full_name() if x.user.get_full_name() else x.user))
|
SourceColumn(
|
||||||
|
source=Comment, label=_('User'),
|
||||||
|
attribute=encapsulate(lambda x: x.user.get_full_name() if x.user.get_full_name() else x.user)
|
||||||
|
)
|
||||||
SourceColumn(source=Comment, label=_('Comment'), attribute='comment')
|
SourceColumn(source=Comment, label=_('Comment'), attribute='comment')
|
||||||
|
|
||||||
ModelPermission.register(
|
ModelPermission.register(
|
||||||
@@ -51,6 +56,14 @@ class DocumentCommentsApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
menu_sidebar.bind_links(links=[link_comment_add], sources=['comments:comments_for_document', 'comments:comment_add', 'comments:comment_delete', 'comments:comment_multiple_delete'])
|
menu_sidebar.bind_links(
|
||||||
|
links=[link_comment_add],
|
||||||
|
sources=[
|
||||||
|
'comments:comments_for_document', 'comments:comment_add',
|
||||||
|
'comments:comment_delete', 'comments:comment_multiple_delete'
|
||||||
|
]
|
||||||
|
)
|
||||||
menu_object.bind_links(links=[link_comment_delete], sources=[Comment])
|
menu_object.bind_links(links=[link_comment_delete], sources=[Comment])
|
||||||
menu_facet.bind_links(links=[link_comments_for_document], sources=[Document])
|
menu_facet.bind_links(
|
||||||
|
links=[link_comments_for_document], sources=[Document]
|
||||||
|
)
|
||||||
|
|||||||
@@ -9,7 +9,19 @@ from .permissions import (
|
|||||||
permission_comment_view
|
permission_comment_view
|
||||||
)
|
)
|
||||||
|
|
||||||
link_comment_add = Link(permissions=[permission_comment_create], text=_('Add comment'), view='comments:comment_add', args='object.pk')
|
link_comment_add = Link(
|
||||||
link_comment_delete = Link(permissions=[permission_comment_delete], tags='dangerous', text=_('Delete'), view='comments:comment_delete', args='object.pk')
|
permissions=[permission_comment_create], text=_('Add comment'),
|
||||||
link_comment_multiple_delete = Link(permissions=[permission_comment_delete], tags='dangerous', text=_('Delete'), view='comments:comment_multiple_delete', args='object.pk')
|
view='comments:comment_add', args='object.pk'
|
||||||
link_comments_for_document = Link(permissions=[permission_comment_view], text=_('Comments'), view='comments:comments_for_document', args='object.pk')
|
)
|
||||||
|
link_comment_delete = Link(
|
||||||
|
permissions=[permission_comment_delete], tags='dangerous',
|
||||||
|
text=_('Delete'), view='comments:comment_delete', args='object.pk'
|
||||||
|
)
|
||||||
|
link_comment_multiple_delete = Link(
|
||||||
|
permissions=[permission_comment_delete], tags='dangerous',
|
||||||
|
text=_('Delete'), view='comments:comment_multiple_delete', args='object.pk'
|
||||||
|
)
|
||||||
|
link_comments_for_document = Link(
|
||||||
|
permissions=[permission_comment_view], text=_('Comments'),
|
||||||
|
view='comments:comments_for_document', args='object.pk'
|
||||||
|
)
|
||||||
|
|||||||
@@ -6,6 +6,12 @@ from permissions import PermissionNamespace
|
|||||||
|
|
||||||
namespace = PermissionNamespace('comments', _('Comments'))
|
namespace = PermissionNamespace('comments', _('Comments'))
|
||||||
|
|
||||||
permission_comment_create = namespace.add_permission(name='comment_create', label=_('Create new comments'))
|
permission_comment_create = namespace.add_permission(
|
||||||
permission_comment_delete = namespace.add_permission(name='comment_delete', label=_('Delete comments'))
|
name='comment_create', label=_('Create new comments')
|
||||||
permission_comment_view = namespace.add_permission(name='comment_view', label=_('View comments'))
|
)
|
||||||
|
permission_comment_delete = namespace.add_permission(
|
||||||
|
name='comment_delete', label=_('Delete comments')
|
||||||
|
)
|
||||||
|
permission_comment_view = namespace.add_permission(
|
||||||
|
name='comment_view', label=_('View comments')
|
||||||
|
)
|
||||||
|
|||||||
@@ -4,8 +4,20 @@ from django.conf.urls import patterns, url
|
|||||||
|
|
||||||
urlpatterns = patterns(
|
urlpatterns = patterns(
|
||||||
'document_comments.views',
|
'document_comments.views',
|
||||||
url(r'^comment/(?P<comment_id>\d+)/delete/$', 'comment_delete', name='comment_delete'),
|
url(
|
||||||
url(r'^comment/multiple/delete/$', 'comment_multiple_delete', name='comment_multiple_delete'),
|
r'^comment/(?P<comment_id>\d+)/delete/$', 'comment_delete',
|
||||||
url(r'^(?P<document_id>\d+)/comment/add/$', 'comment_add', name='comment_add'),
|
name='comment_delete'
|
||||||
url(r'^(?P<document_id>\d+)/comment/list/$', 'comments_for_document', name='comments_for_document'),
|
),
|
||||||
|
url(
|
||||||
|
r'^comment/multiple/delete/$', 'comment_multiple_delete',
|
||||||
|
name='comment_multiple_delete'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^(?P<document_id>\d+)/comment/add/$', 'comment_add',
|
||||||
|
name='comment_add'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^(?P<document_id>\d+)/comment/list/$', 'comments_for_document',
|
||||||
|
name='comments_for_document'
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -82,7 +82,9 @@ def comment_add(request, document_id):
|
|||||||
try:
|
try:
|
||||||
Permission.check_permissions(request.user, [permission_comment_create])
|
Permission.check_permissions(request.user, [permission_comment_create])
|
||||||
except PermissionDenied:
|
except PermissionDenied:
|
||||||
AccessControlList.objects.check_access(permission_comment_create, request.user, document)
|
AccessControlList.objects.check_access(
|
||||||
|
permission_comment_create, request.user, document
|
||||||
|
)
|
||||||
|
|
||||||
post_action_redirect = None
|
post_action_redirect = None
|
||||||
|
|
||||||
@@ -120,7 +122,9 @@ def comments_for_document(request, document_id):
|
|||||||
try:
|
try:
|
||||||
Permission.check_permissions(request.user, [permission_comment_view])
|
Permission.check_permissions(request.user, [permission_comment_view])
|
||||||
except PermissionDenied:
|
except PermissionDenied:
|
||||||
AccessControlList.objects.check_access(permission_comment_view, request.user, document)
|
AccessControlList.objects.check_access(
|
||||||
|
permission_comment_view, request.user, document
|
||||||
|
)
|
||||||
|
|
||||||
return render_to_response('appearance/generic_list.html', {
|
return render_to_response('appearance/generic_list.html', {
|
||||||
'object': document,
|
'object': document,
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ from rest_api.filters import MayanObjectPermissionsFilter
|
|||||||
from rest_api.permissions import MayanPermission
|
from rest_api.permissions import MayanPermission
|
||||||
|
|
||||||
from .models import Index, IndexInstanceNode, IndexTemplateNode
|
from .models import Index, IndexInstanceNode, IndexTemplateNode
|
||||||
from .permissions import (permission_document_indexing_create,
|
from .permissions import (
|
||||||
permission_document_indexing_delete,
|
permission_document_indexing_create, permission_document_indexing_delete,
|
||||||
permission_document_indexing_edit,
|
permission_document_indexing_edit, permission_document_indexing_view
|
||||||
permission_document_indexing_view)
|
)
|
||||||
from .serializers import (IndexInstanceNodeSerializer, IndexSerializer,
|
from .serializers import (
|
||||||
IndexTemplateNodeSerializer)
|
IndexInstanceNodeSerializer, IndexSerializer, IndexTemplateNodeSerializer
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class APIIndexListView(generics.ListCreateAPIView):
|
class APIIndexListView(generics.ListCreateAPIView):
|
||||||
@@ -69,7 +70,8 @@ class APIIndexView(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
|
|
||||||
class APIIndexNodeInstanceDocumentListView(generics.ListAPIView):
|
class APIIndexNodeInstanceDocumentListView(generics.ListAPIView):
|
||||||
"""
|
"""
|
||||||
Returns a list of all the documents contained by a particular index node instance.
|
Returns a list of all the documents contained by a particular index node
|
||||||
|
instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
filter_backends = (MayanObjectPermissionsFilter,)
|
filter_backends = (MayanObjectPermissionsFilter,)
|
||||||
@@ -80,11 +82,18 @@ class APIIndexNodeInstanceDocumentListView(generics.ListAPIView):
|
|||||||
return DocumentSerializer
|
return DocumentSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
index_node_instance = get_object_or_404(IndexInstanceNode, pk=self.kwargs['pk'])
|
index_node_instance = get_object_or_404(
|
||||||
|
IndexInstanceNode, pk=self.kwargs['pk']
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
Permission.check_permissions(self.request.user, [permission_document_indexing_view])
|
Permission.check_permissions(
|
||||||
|
self.request.user, [permission_document_indexing_view]
|
||||||
|
)
|
||||||
except PermissionDenied:
|
except PermissionDenied:
|
||||||
AccessControlList.objects.check_access(permission_document_indexing_view, self.request.user, index_node_instance.index)
|
AccessControlList.objects.check_access(
|
||||||
|
permission_document_indexing_view, self.request.user,
|
||||||
|
index_node_instance.index
|
||||||
|
)
|
||||||
|
|
||||||
return index_node_instance.documents.all()
|
return index_node_instance.documents.all()
|
||||||
|
|
||||||
@@ -142,8 +151,12 @@ class APIDocumentIndexListView(generics.ListAPIView):
|
|||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
document = get_object_or_404(Document, pk=self.kwargs['pk'])
|
document = get_object_or_404(Document, pk=self.kwargs['pk'])
|
||||||
try:
|
try:
|
||||||
Permission.check_permissions(self.request.user, [permission_document_view])
|
Permission.check_permissions(
|
||||||
|
self.request.user, [permission_document_view]
|
||||||
|
)
|
||||||
except PermissionDenied:
|
except PermissionDenied:
|
||||||
AccessControlList.objects.check_access(permission_document_view, self.request.user, document)
|
AccessControlList.objects.check_access(
|
||||||
|
permission_document_view, self.request.user, document
|
||||||
|
)
|
||||||
|
|
||||||
return document.node_instances.all()
|
return document.node_instances.all()
|
||||||
|
|||||||
@@ -59,15 +59,47 @@ class DocumentIndexingApp(MayanAppConfig):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
menu_facet.bind_links(links=[link_document_index_list], sources=[Document])
|
menu_facet.bind_links(
|
||||||
menu_object.bind_links(links=[link_index_setup_edit, link_index_setup_view, link_index_setup_document_types, link_index_setup_delete], sources=[Index])
|
links=[link_document_index_list], sources=[Document]
|
||||||
menu_object.bind_links(links=[link_template_node_create, link_template_node_edit, link_template_node_delete], sources=[IndexTemplateNode])
|
)
|
||||||
|
menu_object.bind_links(
|
||||||
|
links=[
|
||||||
|
link_index_setup_edit, link_index_setup_view,
|
||||||
|
link_index_setup_document_types, link_index_setup_delete
|
||||||
|
], sources=[Index]
|
||||||
|
)
|
||||||
|
menu_object.bind_links(
|
||||||
|
links=[
|
||||||
|
link_template_node_create, link_template_node_edit,
|
||||||
|
link_template_node_delete
|
||||||
|
], sources=[IndexTemplateNode]
|
||||||
|
)
|
||||||
menu_main.bind_links(links=[link_index_main_menu])
|
menu_main.bind_links(links=[link_index_main_menu])
|
||||||
menu_secondary.bind_links(links=[link_index_setup_list, link_index_setup_create], sources=[Index, 'indexing:index_setup_list', 'indexing:index_setup_create'])
|
menu_secondary.bind_links(
|
||||||
|
links=[link_index_setup_list, link_index_setup_create],
|
||||||
|
sources=[
|
||||||
|
Index, 'indexing:index_setup_list',
|
||||||
|
'indexing:index_setup_create'
|
||||||
|
]
|
||||||
|
)
|
||||||
menu_setup.bind_links(links=[link_index_setup])
|
menu_setup.bind_links(links=[link_index_setup])
|
||||||
menu_tools.bind_links(links=[link_rebuild_index_instances])
|
menu_tools.bind_links(links=[link_rebuild_index_instances])
|
||||||
|
|
||||||
post_document_created.connect(document_created_index_update, dispatch_uid='document_created_index_update', sender=Document)
|
post_document_created.connect(
|
||||||
post_save.connect(document_metadata_index_update, dispatch_uid='document_metadata_index_update', sender=DocumentMetadata)
|
document_created_index_update,
|
||||||
post_delete.connect(document_index_delete, dispatch_uid='document_index_delete', sender=Document)
|
dispatch_uid='document_created_index_update', sender=Document
|
||||||
post_delete.connect(document_metadata_index_post_delete, dispatch_uid='document_metadata_index_post_delete', sender=DocumentMetadata)
|
)
|
||||||
|
post_save.connect(
|
||||||
|
document_metadata_index_update,
|
||||||
|
dispatch_uid='document_metadata_index_update',
|
||||||
|
sender=DocumentMetadata
|
||||||
|
)
|
||||||
|
post_delete.connect(
|
||||||
|
document_index_delete, dispatch_uid='document_index_delete',
|
||||||
|
sender=Document
|
||||||
|
)
|
||||||
|
post_delete.connect(
|
||||||
|
document_metadata_index_post_delete,
|
||||||
|
dispatch_uid='document_metadata_index_post_delete',
|
||||||
|
sender=DocumentMetadata
|
||||||
|
)
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ class IndexTemplateNodeForm(forms.ModelForm):
|
|||||||
super(IndexTemplateNodeForm, self).__init__(*args, **kwargs)
|
super(IndexTemplateNodeForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['index'].widget = forms.widgets.HiddenInput()
|
self.fields['index'].widget = forms.widgets.HiddenInput()
|
||||||
self.fields['parent'].widget = forms.widgets.HiddenInput()
|
self.fields['parent'].widget = forms.widgets.HiddenInput()
|
||||||
self.fields['expression'].help_text = ' '.join([unicode(self.fields['expression'].help_text), ModelAttribute.help_text_for(Document, type_names=['indexing'])])
|
self.fields['expression'].help_text = ' '.join(
|
||||||
|
[
|
||||||
|
unicode(self.fields['expression'].help_text),
|
||||||
|
ModelAttribute.help_text_for(Document, type_names=['indexing'])
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ('parent', 'index', 'expression', 'enabled', 'link_documents')
|
fields = ('parent', 'index', 'expression', 'enabled', 'link_documents')
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ from .tasks import task_delete_empty_index_nodes, task_index_document
|
|||||||
|
|
||||||
|
|
||||||
def document_created_index_update(sender, **kwargs):
|
def document_created_index_update(sender, **kwargs):
|
||||||
task_index_document.apply_async(kwargs=dict(document_id=kwargs['instance'].pk))
|
task_index_document.apply_async(
|
||||||
|
kwargs=dict(document_id=kwargs['instance'].pk)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def document_index_delete(sender, **kwargs):
|
def document_index_delete(sender, **kwargs):
|
||||||
@@ -12,8 +14,12 @@ def document_index_delete(sender, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
def document_metadata_index_update(sender, **kwargs):
|
def document_metadata_index_update(sender, **kwargs):
|
||||||
task_index_document.apply_async(kwargs=dict(document_id=kwargs['instance'].document.pk))
|
task_index_document.apply_async(
|
||||||
|
kwargs=dict(document_id=kwargs['instance'].document.pk)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def document_metadata_index_post_delete(sender, **kwargs):
|
def document_metadata_index_post_delete(sender, **kwargs):
|
||||||
task_index_document.apply_async(kwargs=dict(document_id=kwargs['instance'].document.pk))
|
task_index_document.apply_async(
|
||||||
|
kwargs=dict(document_id=kwargs['instance'].document.pk)
|
||||||
|
)
|
||||||
|
|||||||
@@ -17,22 +17,65 @@ def is_not_root_node(context):
|
|||||||
return not context['resolved_object'].is_root_node()
|
return not context['resolved_object'].is_root_node()
|
||||||
|
|
||||||
|
|
||||||
link_document_index_list = Link(permissions=[permission_document_view], text=_('Indexes'), view='indexing:document_index_list', args='object.pk')
|
link_document_index_list = Link(
|
||||||
link_index_list = Link(permissions=[permission_document_indexing_view], text=_('Index list'), view='indexing:index_list')
|
permissions=[permission_document_view], text=_('Indexes'),
|
||||||
link_index_main_menu = Link(icon='fa fa-list-ul', text=_('Indexes'), view='indexing:index_list')
|
view='indexing:document_index_list', args='object.pk'
|
||||||
link_index_setup = Link(icon='fa fa-list-ul', permissions=[permission_document_indexing_setup], text=_('Indexes'), view='indexing:index_setup_list')
|
)
|
||||||
link_index_setup_list = Link(permissions=[permission_document_indexing_setup], text=_('Indexes'), view='indexing:index_setup_list')
|
link_index_list = Link(
|
||||||
link_index_setup_create = Link(permissions=[permission_document_indexing_create], text=_('Create index'), view='indexing:index_setup_create')
|
permissions=[permission_document_indexing_view], text=_('Index list'),
|
||||||
link_index_setup_edit = Link(permissions=[permission_document_indexing_edit], text=_('Edit'), view='indexing:index_setup_edit', args='resolved_object.pk')
|
view='indexing:index_list'
|
||||||
link_index_setup_delete = Link(permissions=[permission_document_indexing_delete], tags='dangerous', text=_('Delete'), view='indexing:index_setup_delete', args='resolved_object.pk')
|
)
|
||||||
link_index_setup_view = Link(permissions=[permission_document_indexing_setup], text=_('Tree template'), view='indexing:index_setup_view', args='resolved_object.pk')
|
link_index_main_menu = Link(
|
||||||
link_index_setup_document_types = Link(permissions=[permission_document_indexing_edit], text=_('Document types'), view='indexing:index_setup_document_types', args='resolved_object.pk')
|
icon='fa fa-list-ul', text=_('Indexes'), view='indexing:index_list'
|
||||||
|
)
|
||||||
|
link_index_setup = Link(
|
||||||
|
icon='fa fa-list-ul', permissions=[permission_document_indexing_setup],
|
||||||
|
text=_('Indexes'), view='indexing:index_setup_list'
|
||||||
|
)
|
||||||
|
link_index_setup_list = Link(
|
||||||
|
permissions=[permission_document_indexing_setup], text=_('Indexes'),
|
||||||
|
view='indexing:index_setup_list'
|
||||||
|
)
|
||||||
|
link_index_setup_create = Link(
|
||||||
|
permissions=[permission_document_indexing_create], text=_('Create index'),
|
||||||
|
view='indexing:index_setup_create'
|
||||||
|
)
|
||||||
|
link_index_setup_edit = Link(
|
||||||
|
permissions=[permission_document_indexing_edit], text=_('Edit'),
|
||||||
|
view='indexing:index_setup_edit', args='resolved_object.pk'
|
||||||
|
)
|
||||||
|
link_index_setup_delete = Link(
|
||||||
|
permissions=[permission_document_indexing_delete], tags='dangerous',
|
||||||
|
text=_('Delete'), view='indexing:index_setup_delete',
|
||||||
|
args='resolved_object.pk'
|
||||||
|
)
|
||||||
|
link_index_setup_view = Link(
|
||||||
|
permissions=[permission_document_indexing_setup], text=_('Tree template'),
|
||||||
|
view='indexing:index_setup_view', args='resolved_object.pk'
|
||||||
|
)
|
||||||
|
link_index_setup_document_types = Link(
|
||||||
|
permissions=[permission_document_indexing_edit], text=_('Document types'),
|
||||||
|
view='indexing:index_setup_document_types', args='resolved_object.pk'
|
||||||
|
)
|
||||||
link_rebuild_index_instances = Link(
|
link_rebuild_index_instances = Link(
|
||||||
icon='fa fa-database',
|
icon='fa fa-database',
|
||||||
description=_('Deletes and creates from scratch all the document indexes.'),
|
description=_('Deletes and creates from scratch all the document indexes.'),
|
||||||
permissions=[permission_document_indexing_rebuild_indexes],
|
permissions=[permission_document_indexing_rebuild_indexes],
|
||||||
text=_('Rebuild indexes'), view='indexing:rebuild_index_instances'
|
text=_('Rebuild indexes'), view='indexing:rebuild_index_instances'
|
||||||
)
|
)
|
||||||
link_template_node_create = Link(permissions=[permission_document_indexing_setup], text=_('New child node'), view='indexing:template_node_create', args='resolved_object.pk')
|
link_template_node_create = Link(
|
||||||
link_template_node_edit = Link(condition=is_not_root_node, permissions=[permission_document_indexing_setup], text=_('Edit'), view='indexing:template_node_edit', args='resolved_object.pk')
|
permissions=[permission_document_indexing_setup], text=_('New child node'),
|
||||||
link_template_node_delete = Link(condition=is_not_root_node, permissions=[permission_document_indexing_setup], tags='dangerous', text=_('Delete'), view='indexing:template_node_delete', args='resolved_object.pk')
|
view='indexing:template_node_create', args='resolved_object.pk'
|
||||||
|
)
|
||||||
|
link_template_node_edit = Link(
|
||||||
|
condition=is_not_root_node,
|
||||||
|
permissions=[permission_document_indexing_setup], text=_('Edit'),
|
||||||
|
view='indexing:template_node_edit', args='resolved_object.pk'
|
||||||
|
)
|
||||||
|
link_template_node_delete = Link(
|
||||||
|
condition=is_not_root_node, permissions=[
|
||||||
|
permission_document_indexing_setup
|
||||||
|
],
|
||||||
|
tags='dangerous', text=_('Delete'), view='indexing:template_node_delete',
|
||||||
|
args='resolved_object.pk'
|
||||||
|
)
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ class IndexInstanceNodeManager(models.Manager):
|
|||||||
parent = instance_node.parent
|
parent = instance_node.parent
|
||||||
if parent:
|
if parent:
|
||||||
instance_node.delete()
|
instance_node.delete()
|
||||||
IndexInstanceNodeManager.delete_empty_index_nodes_recursive(parent)
|
IndexInstanceNodeManager.delete_empty_index_nodes_recursive(
|
||||||
|
parent
|
||||||
|
)
|
||||||
|
|
||||||
def cascade_eval(self, document, template_node, parent_index_instance=None):
|
def cascade_eval(self, document, template_node, parent_index_instance=None):
|
||||||
"""
|
"""
|
||||||
@@ -41,14 +43,26 @@ class IndexInstanceNodeManager(models.Manager):
|
|||||||
|
|
||||||
if template_node.enabled:
|
if template_node.enabled:
|
||||||
try:
|
try:
|
||||||
result = eval(template_node.expression, {'document': document}, setting_available_indexing_functions.value)
|
result = eval(
|
||||||
|
template_node.expression, {'document': document},
|
||||||
|
setting_available_indexing_functions.value
|
||||||
|
)
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
error_message = _('Error indexing document: %(document)s; expression: %(expression)s; %(exception)s') % {
|
error_message = _(
|
||||||
'document': document, 'expression': template_node.expression, 'exception': exception}
|
'Error indexing document: %(document)s; expression: '
|
||||||
|
'%(expression)s; %(exception)s'
|
||||||
|
) % {
|
||||||
|
'document': document,
|
||||||
|
'expression': template_node.expression,
|
||||||
|
'exception': exception
|
||||||
|
}
|
||||||
logger.debug(error_message)
|
logger.debug(error_message)
|
||||||
else:
|
else:
|
||||||
if result:
|
if result:
|
||||||
index_instance, created = self.get_or_create(index_template_node=template_node, value=result, parent=parent_index_instance)
|
index_instance, created = self.get_or_create(
|
||||||
|
index_template_node=template_node, value=result,
|
||||||
|
parent=parent_index_instance
|
||||||
|
)
|
||||||
|
|
||||||
if template_node.link_documents:
|
if template_node.link_documents:
|
||||||
index_instance.documents.add(document)
|
index_instance.documents.add(document)
|
||||||
@@ -66,7 +80,9 @@ class IndexInstanceNodeManager(models.Manager):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
for instance_node in self.filter(documents__isnull=True, parent__isnull=False):
|
for instance_node in self.filter(documents__isnull=True, parent__isnull=False):
|
||||||
IndexInstanceNodeManager.delete_empty_index_nodes_recursive(instance_node)
|
IndexInstanceNodeManager.delete_empty_index_nodes_recursive(
|
||||||
|
instance_node
|
||||||
|
)
|
||||||
|
|
||||||
def index_document(self, document):
|
def index_document(self, document):
|
||||||
"""
|
"""
|
||||||
@@ -80,7 +96,9 @@ class IndexInstanceNodeManager(models.Manager):
|
|||||||
|
|
||||||
# Only update indexes where the document type is found
|
# Only update indexes where the document type is found
|
||||||
for index in Index.objects.filter(enabled=True, document_types=document.document_type):
|
for index in Index.objects.filter(enabled=True, document_types=document.document_type):
|
||||||
root_instance, created = self.get_or_create(index_template_node=index.template_root, parent=None)
|
root_instance, created = self.get_or_create(
|
||||||
|
index_template_node=index.template_root, parent=None
|
||||||
|
)
|
||||||
for template_node in index.template_root.get_children():
|
for template_node in index.template_root.get_children():
|
||||||
self.cascade_eval(document, template_node, root_instance)
|
self.cascade_eval(document, template_node, root_instance)
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,20 @@ from .managers import IndexManager, IndexInstanceNodeManager
|
|||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Index(models.Model):
|
class Index(models.Model):
|
||||||
label = models.CharField(max_length=128, unique=True, verbose_name=_('Label'))
|
label = models.CharField(
|
||||||
enabled = models.BooleanField(default=True, help_text=_('Causes this index to be visible and updated when document data changes.'), verbose_name=_('Enabled'))
|
max_length=128, unique=True, verbose_name=_('Label')
|
||||||
document_types = models.ManyToManyField(DocumentType, verbose_name=_('Document types'))
|
)
|
||||||
|
enabled = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
help_text=_(
|
||||||
|
'Causes this index to be visible and updated when document data '
|
||||||
|
'changes.'
|
||||||
|
),
|
||||||
|
verbose_name=_('Enabled')
|
||||||
|
)
|
||||||
|
document_types = models.ManyToManyField(
|
||||||
|
DocumentType, verbose_name=_('Document types')
|
||||||
|
)
|
||||||
|
|
||||||
objects = IndexManager()
|
objects = IndexManager()
|
||||||
|
|
||||||
@@ -34,7 +45,10 @@ class Index(models.Model):
|
|||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
try:
|
try:
|
||||||
return reverse('indexing:index_instance_node_view', args=[self.instance_root.pk])
|
return reverse(
|
||||||
|
'indexing:index_instance_node_view',
|
||||||
|
args=[self.instance_root.pk]
|
||||||
|
)
|
||||||
except IndexInstanceNode.DoesNotExist:
|
except IndexInstanceNode.DoesNotExist:
|
||||||
return '#'
|
return '#'
|
||||||
|
|
||||||
@@ -44,7 +58,9 @@ class Index(models.Model):
|
|||||||
IndexTemplateNode.objects.get_or_create(parent=None, index=self)
|
IndexTemplateNode.objects.get_or_create(parent=None, index=self)
|
||||||
|
|
||||||
def get_document_types_names(self):
|
def get_document_types_names(self):
|
||||||
return ', '.join([unicode(document_type) for document_type in self.document_types.all()] or ['None'])
|
return ', '.join(
|
||||||
|
[unicode(document_type) for document_type in self.document_types.all()] or ['None']
|
||||||
|
)
|
||||||
|
|
||||||
def get_instance_node_count(self):
|
def get_instance_node_count(self):
|
||||||
try:
|
try:
|
||||||
@@ -59,11 +75,31 @@ class Index(models.Model):
|
|||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class IndexTemplateNode(MPTTModel):
|
class IndexTemplateNode(MPTTModel):
|
||||||
parent = TreeForeignKey('self', null=True, blank=True)
|
parent = TreeForeignKey('self', blank=True, null=True)
|
||||||
index = models.ForeignKey(Index, verbose_name=_('Index'), related_name='node_templates')
|
index = models.ForeignKey(
|
||||||
expression = models.CharField(max_length=128, verbose_name=_('Indexing expression'), help_text=_('Enter a python string expression to be evaluated.'))
|
Index, related_name='node_templates', verbose_name=_('Index')
|
||||||
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'), help_text=_('Causes this node to be visible and updated when document data changes.'))
|
)
|
||||||
link_documents = models.BooleanField(default=False, verbose_name=_('Link documents'), help_text=_('Check this option to have this node act as a container for documents and not as a parent for further nodes.'))
|
expression = models.CharField(
|
||||||
|
max_length=128,
|
||||||
|
help_text=_('Enter a python string expression to be evaluated.'),
|
||||||
|
verbose_name=_('Indexing expression')
|
||||||
|
)
|
||||||
|
enabled = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
help_text=_(
|
||||||
|
'Causes this node to be visible and updated when document data '
|
||||||
|
'changes.'
|
||||||
|
),
|
||||||
|
verbose_name=_('Enabled')
|
||||||
|
)
|
||||||
|
link_documents = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text=_(
|
||||||
|
'Check this option to have this node act as a container for '
|
||||||
|
'documents and not as a parent for further nodes.'
|
||||||
|
),
|
||||||
|
verbose_name=_('Link documents')
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.is_root_node():
|
if self.is_root_node():
|
||||||
@@ -79,9 +115,16 @@ class IndexTemplateNode(MPTTModel):
|
|||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class IndexInstanceNode(MPTTModel):
|
class IndexInstanceNode(MPTTModel):
|
||||||
parent = TreeForeignKey('self', null=True, blank=True)
|
parent = TreeForeignKey('self', null=True, blank=True)
|
||||||
index_template_node = models.ForeignKey(IndexTemplateNode, related_name='node_instance', verbose_name=_('Index template node'))
|
index_template_node = models.ForeignKey(
|
||||||
value = models.CharField(max_length=128, blank=True, verbose_name=_('Value'))
|
IndexTemplateNode, related_name='node_instance',
|
||||||
documents = models.ManyToManyField(Document, related_name='node_instances', verbose_name=_('Documents'))
|
verbose_name=_('Index template node')
|
||||||
|
)
|
||||||
|
value = models.CharField(
|
||||||
|
blank=True, max_length=128, verbose_name=_('Value')
|
||||||
|
)
|
||||||
|
documents = models.ManyToManyField(
|
||||||
|
Document, related_name='node_instances', verbose_name=_('Documents')
|
||||||
|
)
|
||||||
|
|
||||||
objects = IndexInstanceNodeManager()
|
objects = IndexInstanceNodeManager()
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,21 @@ from permissions import PermissionNamespace
|
|||||||
|
|
||||||
namespace = PermissionNamespace('document_indexing', _('Indexing'))
|
namespace = PermissionNamespace('document_indexing', _('Indexing'))
|
||||||
|
|
||||||
permission_document_indexing_setup = namespace.add_permission(name='document_index_setup', label=_('Configure document indexes'))
|
permission_document_indexing_setup = namespace.add_permission(
|
||||||
permission_document_indexing_create = namespace.add_permission(name='document_index_create', label=_('Create new document indexes'))
|
name='document_index_setup', label=_('Configure document indexes')
|
||||||
permission_document_indexing_edit = namespace.add_permission(name='document_index_edit', label=_('Edit document indexes'))
|
)
|
||||||
permission_document_indexing_delete = namespace.add_permission(name='document_index_delete', label=_('Delete document indexes'))
|
permission_document_indexing_create = namespace.add_permission(
|
||||||
permission_document_indexing_view = namespace.add_permission(name='document_index_view', label=_('View document indexes'))
|
name='document_index_create', label=_('Create new document indexes')
|
||||||
permission_document_indexing_rebuild_indexes = namespace.add_permission(name='document_rebuild_indexes', label=_('Rebuild document indexes'))
|
)
|
||||||
|
permission_document_indexing_edit = namespace.add_permission(
|
||||||
|
name='document_index_edit', label=_('Edit document indexes')
|
||||||
|
)
|
||||||
|
permission_document_indexing_delete = namespace.add_permission(
|
||||||
|
name='document_index_delete', label=_('Delete document indexes')
|
||||||
|
)
|
||||||
|
permission_document_indexing_view = namespace.add_permission(
|
||||||
|
name='document_index_view', label=_('View document indexes')
|
||||||
|
)
|
||||||
|
permission_document_indexing_rebuild_indexes = namespace.add_permission(
|
||||||
|
name='document_rebuild_indexes', label=_('Rebuild document indexes')
|
||||||
|
)
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ IndexInstanceNodeSerializer.base_fields['children'] = IndexInstanceNodeSerialize
|
|||||||
|
|
||||||
class IndexTemplateNodeSerializer(serializers.ModelSerializer):
|
class IndexTemplateNodeSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ('id', 'parent', 'index', 'expression', 'enabled', 'link_documents', 'level')
|
fields = (
|
||||||
|
'id', 'parent', 'index', 'expression', 'enabled', 'link_documents',
|
||||||
|
'level'
|
||||||
|
)
|
||||||
model = IndexTemplateNode
|
model = IndexTemplateNode
|
||||||
|
|
||||||
|
|
||||||
@@ -30,5 +33,8 @@ class IndexSerializer(serializers.ModelSerializer):
|
|||||||
instance_root = IndexInstanceNodeSerializer(read_only=True)
|
instance_root = IndexInstanceNodeSerializer(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ('id', 'label', 'enabled', 'document_types', 'node_templates', 'instance_root')
|
fields = (
|
||||||
|
'id', 'label', 'enabled', 'document_types', 'node_templates',
|
||||||
|
'instance_root'
|
||||||
|
)
|
||||||
model = Index
|
model = Index
|
||||||
|
|||||||
@@ -8,4 +8,7 @@ available_indexing_functions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace = Namespace(name='document_indexing', label=_('Indexing'))
|
namespace = Namespace(name='document_indexing', label=_('Indexing'))
|
||||||
setting_available_indexing_functions = namespace.add_setting(global_name='DOCUMENT_INDEXING_AVAILABLE_INDEXING_FUNCTIONS', default=available_indexing_functions)
|
setting_available_indexing_functions = namespace.add_setting(
|
||||||
|
global_name='DOCUMENT_INDEXING_AVAILABLE_INDEXING_FUNCTIONS',
|
||||||
|
default=available_indexing_functions
|
||||||
|
)
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ logger = logging.getLogger(__name__)
|
|||||||
@app.task(bind=True, default_retry_delay=RETRY_DELAY, max_retries=None, ignore_result=True)
|
@app.task(bind=True, default_retry_delay=RETRY_DELAY, max_retries=None, ignore_result=True)
|
||||||
def task_delete_empty_index_nodes(self):
|
def task_delete_empty_index_nodes(self):
|
||||||
try:
|
try:
|
||||||
rebuild_lock = Lock.acquire_lock('document_indexing_task_do_rebuild_all_indexes')
|
rebuild_lock = Lock.acquire_lock(
|
||||||
|
'document_indexing_task_do_rebuild_all_indexes'
|
||||||
|
)
|
||||||
except LockError as exception:
|
except LockError as exception:
|
||||||
# A rebuild is happening, retry later
|
# A rebuild is happening, retry later
|
||||||
raise self.retry(exc=exception)
|
raise self.retry(exc=exception)
|
||||||
@@ -31,13 +33,17 @@ def task_delete_empty_index_nodes(self):
|
|||||||
@app.task(bind=True, default_retry_delay=RETRY_DELAY, max_retries=None, ignore_result=True)
|
@app.task(bind=True, default_retry_delay=RETRY_DELAY, max_retries=None, ignore_result=True)
|
||||||
def task_index_document(self, document_id):
|
def task_index_document(self, document_id):
|
||||||
try:
|
try:
|
||||||
rebuild_lock = Lock.acquire_lock('document_indexing_task_do_rebuild_all_indexes')
|
rebuild_lock = Lock.acquire_lock(
|
||||||
|
'document_indexing_task_do_rebuild_all_indexes'
|
||||||
|
)
|
||||||
except LockError as exception:
|
except LockError as exception:
|
||||||
# A rebuild is happening, retry later
|
# A rebuild is happening, retry later
|
||||||
raise self.retry(exc=exception)
|
raise self.retry(exc=exception)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
lock = Lock.acquire_lock('document_indexing_task_update_index_document_%d' % document_id)
|
lock = Lock.acquire_lock(
|
||||||
|
'document_indexing_task_update_index_document_%d' % document_id
|
||||||
|
)
|
||||||
except LockError as exception:
|
except LockError as exception:
|
||||||
# This document is being reindexed by another task, retry later
|
# This document is being reindexed by another task, retry later
|
||||||
raise self.retry(exc=exception)
|
raise self.retry(exc=exception)
|
||||||
@@ -45,13 +51,17 @@ def task_index_document(self, document_id):
|
|||||||
try:
|
try:
|
||||||
document = Document.objects.get(pk=document_id)
|
document = Document.objects.get(pk=document_id)
|
||||||
except Document.DoesNotExist:
|
except Document.DoesNotExist:
|
||||||
# Document was deleted before we could execute, abort about updating
|
# Document was deleted before we could execute, abort about
|
||||||
|
# updating
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
IndexInstanceNode.objects.index_document(document)
|
IndexInstanceNode.objects.index_document(document)
|
||||||
except OperationalError as exception:
|
except OperationalError as exception:
|
||||||
logger.warning('Operational error while trying to index document: %s; %s', document, exception)
|
logger.warning(
|
||||||
|
'Operational error while trying to index document: %s; %s',
|
||||||
|
document, exception
|
||||||
|
)
|
||||||
lock.release()
|
lock.release()
|
||||||
raise self.retry(exc=exception)
|
raise self.retry(exc=exception)
|
||||||
else:
|
else:
|
||||||
@@ -69,7 +79,9 @@ def task_do_rebuild_all_indexes(self):
|
|||||||
raise self.retry()
|
raise self.retry()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
lock = Lock.acquire_lock('document_indexing_task_do_rebuild_all_indexes')
|
lock = Lock.acquire_lock(
|
||||||
|
'document_indexing_task_do_rebuild_all_indexes'
|
||||||
|
)
|
||||||
except LockError as exception:
|
except LockError as exception:
|
||||||
# Another rebuild is happening, retry later
|
# Another rebuild is happening, retry later
|
||||||
raise self.retry(exc=exception)
|
raise self.retry(exc=exception)
|
||||||
|
|||||||
@@ -12,14 +12,18 @@ from .models import Index, IndexInstanceNode, IndexTemplateNode
|
|||||||
|
|
||||||
class IndexTestCase(TestCase):
|
class IndexTestCase(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.document_type = DocumentType.objects.create(label=TEST_DOCUMENT_TYPE)
|
self.document_type = DocumentType.objects.create(
|
||||||
|
label=TEST_DOCUMENT_TYPE
|
||||||
|
)
|
||||||
|
|
||||||
ocr_settings = self.document_type.ocr_settings
|
ocr_settings = self.document_type.ocr_settings
|
||||||
ocr_settings.auto_ocr = False
|
ocr_settings.auto_ocr = False
|
||||||
ocr_settings.save()
|
ocr_settings.save()
|
||||||
|
|
||||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
||||||
self.document = self.document_type.new_document(file_object=File(file_object))
|
self.document = self.document_type.new_document(
|
||||||
|
file_object=File(file_object)
|
||||||
|
)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
for document_type in DocumentType.objects.all():
|
for document_type in DocumentType.objects.all():
|
||||||
@@ -27,7 +31,9 @@ class IndexTestCase(TestCase):
|
|||||||
|
|
||||||
def test_indexing(self):
|
def test_indexing(self):
|
||||||
metadata_type = MetadataType.objects.create(name='test', label='test')
|
metadata_type = MetadataType.objects.create(name='test', label='test')
|
||||||
DocumentTypeMetadataType.objects.create(document_type=self.document_type, metadata_type=metadata_type)
|
DocumentTypeMetadataType.objects.create(
|
||||||
|
document_type=self.document_type, metadata_type=metadata_type
|
||||||
|
)
|
||||||
|
|
||||||
# Create empty index
|
# Create empty index
|
||||||
index = Index.objects.create(label='test')
|
index = Index.objects.create(label='test')
|
||||||
|
|||||||
@@ -40,4 +40,8 @@ class MailerApp(MayanAppConfig):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
menu_object.bind_links(links=[link_send_document_link, link_send_document], sources=[Document])
|
menu_object.bind_links(
|
||||||
|
links=[
|
||||||
|
link_send_document_link, link_send_document
|
||||||
|
], sources=[Document]
|
||||||
|
)
|
||||||
|
|||||||
@@ -22,4 +22,6 @@ class DocumentMailForm(forms.Form):
|
|||||||
|
|
||||||
email = forms.EmailField(label=_('Email address'))
|
email = forms.EmailField(label=_('Email address'))
|
||||||
subject = forms.CharField(label=_('Subject'), required=False)
|
subject = forms.CharField(label=_('Subject'), required=False)
|
||||||
body = forms.CharField(label=_('Body'), widget=forms.widgets.Textarea(), required=False)
|
body = forms.CharField(
|
||||||
|
label=_('Body'), widget=forms.widgets.Textarea(), required=False
|
||||||
|
)
|
||||||
|
|||||||
@@ -4,7 +4,15 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
|
|
||||||
from navigation import Link
|
from navigation import Link
|
||||||
|
|
||||||
from .permissions import permission_mailing_link, permission_mailing_send_document
|
from .permissions import (
|
||||||
|
permission_mailing_link, permission_mailing_send_document
|
||||||
|
)
|
||||||
|
|
||||||
link_send_document = Link(permissions=[permission_mailing_send_document], text=_('Email document'), view='mailer:send_document', args='object.pk')
|
link_send_document = Link(
|
||||||
link_send_document_link = Link(permissions=[permission_mailing_link], text=_('Email link'), view='mailer:send_document_link', args='object.pk')
|
permissions=[permission_mailing_send_document], text=_('Email document'),
|
||||||
|
view='mailer:send_document', args='object.pk'
|
||||||
|
)
|
||||||
|
link_send_document_link = Link(
|
||||||
|
permissions=[permission_mailing_link], text=_('Email link'),
|
||||||
|
view='mailer:send_document_link', args='object.pk'
|
||||||
|
)
|
||||||
|
|||||||
@@ -6,5 +6,9 @@ from permissions import PermissionNamespace
|
|||||||
|
|
||||||
namespace = PermissionNamespace('mailing', _('Mailing'))
|
namespace = PermissionNamespace('mailing', _('Mailing'))
|
||||||
|
|
||||||
permission_mailing_link = namespace.add_permission(name='mail_link', label=_('Send document link via email'))
|
permission_mailing_link = namespace.add_permission(
|
||||||
permission_mailing_send_document = namespace.add_permission(name='mail_document', label=_('Send document via email'))
|
name='mail_link', label=_('Send document link via email')
|
||||||
|
)
|
||||||
|
permission_mailing_send_document = namespace.add_permission(
|
||||||
|
name='mail_document', label=_('Send document via email')
|
||||||
|
)
|
||||||
|
|||||||
@@ -6,11 +6,15 @@ from mayan.celery import app
|
|||||||
|
|
||||||
@app.task(ignore_result=True)
|
@app.task(ignore_result=True)
|
||||||
def task_send_document(subject_text, body_text_content, sender, recipient, document_id, as_attachment=False):
|
def task_send_document(subject_text, body_text_content, sender, recipient, document_id, as_attachment=False):
|
||||||
email_msg = EmailMultiAlternatives(subject_text, body_text_content, sender, [recipient])
|
email_msg = EmailMultiAlternatives(
|
||||||
|
subject_text, body_text_content, sender, [recipient]
|
||||||
|
)
|
||||||
|
|
||||||
if as_attachment:
|
if as_attachment:
|
||||||
document = Document.objects.get(pk=document_id)
|
document = Document.objects.get(pk=document_id)
|
||||||
with document.open() as descriptor:
|
with document.open() as descriptor:
|
||||||
email_msg.attach(document.label, descriptor.read(), document.file_mimetype)
|
email_msg.attach(
|
||||||
|
document.label, descriptor.read(), document.file_mimetype
|
||||||
|
)
|
||||||
|
|
||||||
email_msg.send()
|
email_msg.send()
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ from django.conf.urls import patterns, url
|
|||||||
|
|
||||||
urlpatterns = patterns(
|
urlpatterns = patterns(
|
||||||
'mailer.views',
|
'mailer.views',
|
||||||
url(r'^(?P<document_id>\d+)/send/link/$', 'send_document_link', name='send_document_link'),
|
url(
|
||||||
url(r'^(?P<document_id>\d+)/send/document/$', 'send_document_link', {'as_attachment': True}, 'send_document'),
|
r'^(?P<document_id>\d+)/send/link/$', 'send_document_link',
|
||||||
|
name='send_document_link'
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r'^(?P<document_id>\d+)/send/document/$', 'send_document_link',
|
||||||
|
{'as_attachment': True}, 'send_document'
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ class MetadataForm(forms.Form):
|
|||||||
attrs={'readonly': 'readonly'}
|
attrs={'readonly': 'readonly'}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
id = forms.CharField(label=_('ID'), widget=forms.HiddenInput)
|
id = forms.CharField(label=_('ID'), widget=forms.HiddenInput)
|
||||||
|
|
||||||
name = forms.CharField(
|
name = forms.CharField(
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ def metadata_remove(request, document_id=None, document_id_list=None):
|
|||||||
try:
|
try:
|
||||||
Permission.check_permissions(
|
Permission.check_permissions(
|
||||||
request.user, [permission_metadata_document_remove]
|
request.user, [permission_metadata_document_remove]
|
||||||
)
|
)
|
||||||
except PermissionDenied:
|
except PermissionDenied:
|
||||||
documents = AccessControlList.objects.filter_by_access(
|
documents = AccessControlList.objects.filter_by_access(
|
||||||
permission_metadata_document_remove, request.user, documents
|
permission_metadata_document_remove, request.user, documents
|
||||||
|
|||||||
@@ -77,12 +77,16 @@ class Menu(object):
|
|||||||
if source:
|
if source:
|
||||||
resolved_navigation_object_list = [source]
|
resolved_navigation_object_list = [source]
|
||||||
else:
|
else:
|
||||||
navigation_object_list = context.get('navigation_object_list', ['object'])
|
navigation_object_list = context.get(
|
||||||
|
'navigation_object_list', ['object']
|
||||||
|
)
|
||||||
|
|
||||||
# Multiple objects
|
# Multiple objects
|
||||||
for navigation_object in navigation_object_list:
|
for navigation_object in navigation_object_list:
|
||||||
try:
|
try:
|
||||||
resolved_navigation_object_list.append(Variable(navigation_object).resolve(context))
|
resolved_navigation_object_list.append(
|
||||||
|
Variable(navigation_object).resolve(context)
|
||||||
|
)
|
||||||
except VariableDoesNotExist:
|
except VariableDoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -93,10 +97,14 @@ class Menu(object):
|
|||||||
try:
|
try:
|
||||||
if inspect.isclass(bound_source) and type(resolved_navigation_object) == bound_source or source == CombinedSource(obj=resolved_navigation_object.__class__, view=current_view):
|
if inspect.isclass(bound_source) and type(resolved_navigation_object) == bound_source or source == CombinedSource(obj=resolved_navigation_object.__class__, view=current_view):
|
||||||
for link in links:
|
for link in links:
|
||||||
resolved_link = link.resolve(context=context, resolved_object=resolved_navigation_object)
|
resolved_link = link.resolve(
|
||||||
|
context=context,
|
||||||
|
resolved_object=resolved_navigation_object
|
||||||
|
)
|
||||||
if resolved_link:
|
if resolved_link:
|
||||||
resolved_links.append(resolved_link)
|
resolved_links.append(resolved_link)
|
||||||
break # No need for further content object match testing
|
# No need for further content object match testing
|
||||||
|
break
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# When source is a dictionary
|
# When source is a dictionary
|
||||||
pass
|
pass
|
||||||
@@ -164,7 +172,9 @@ class Link(object):
|
|||||||
# access to the instance.
|
# access to the instance.
|
||||||
if resolved_object:
|
if resolved_object:
|
||||||
try:
|
try:
|
||||||
AccessControlList.objects.check_access(self.permissions, request.user, resolved_object)
|
AccessControlList.objects.check_access(
|
||||||
|
self.permissions, request.user, resolved_object
|
||||||
|
)
|
||||||
except PermissionDenied:
|
except PermissionDenied:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
@@ -205,7 +215,9 @@ class Link(object):
|
|||||||
kwargs = {key: Variable(value) for key, value in kwargs.iteritems()}
|
kwargs = {key: Variable(value) for key, value in kwargs.iteritems()}
|
||||||
|
|
||||||
# Use Django's exact {% url %} code to resolve the link
|
# Use Django's exact {% url %} code to resolve the link
|
||||||
node = URLNode(view_name=view_name, args=args, kwargs=kwargs, asvar=None)
|
node = URLNode(
|
||||||
|
view_name=view_name, args=args, kwargs=kwargs, asvar=None
|
||||||
|
)
|
||||||
|
|
||||||
resolved_link.url = node.render(context)
|
resolved_link.url = node.render(context)
|
||||||
|
|
||||||
@@ -218,7 +230,18 @@ class Link(object):
|
|||||||
# Lets a new link keep the same URL query string of the current URL
|
# Lets a new link keep the same URL query string of the current URL
|
||||||
if self.keep_query:
|
if self.keep_query:
|
||||||
# Sometimes we are required to remove a key from the URL QS
|
# Sometimes we are required to remove a key from the URL QS
|
||||||
previous_path = smart_unicode(urllib.unquote_plus(smart_str(request.get_full_path()) or smart_str(request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL)))))
|
previous_path = smart_unicode(
|
||||||
|
urllib.unquote_plus(
|
||||||
|
smart_str(
|
||||||
|
request.get_full_path()
|
||||||
|
) or smart_str(
|
||||||
|
request.META.get(
|
||||||
|
'HTTP_REFERER',
|
||||||
|
reverse(settings.LOGIN_REDIRECT_URL)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
query_string = urlparse.urlparse(previous_path).query
|
query_string = urlparse.urlparse(previous_path).query
|
||||||
parsed_query_string = urlparse.parse_qs(query_string)
|
parsed_query_string = urlparse.parse_qs(query_string)
|
||||||
|
|
||||||
@@ -228,7 +251,10 @@ class Link(object):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
resolved_link.url = '%s?%s' % (urlquote(resolved_link.url), urlencode(parsed_query_string, doseq=True))
|
resolved_link.url = '%s?%s' % (
|
||||||
|
urlquote(resolved_link.url),
|
||||||
|
urlencode(parsed_query_string, doseq=True)
|
||||||
|
)
|
||||||
|
|
||||||
# Helps highligh in the UI the current link in effect
|
# Helps highligh in the UI the current link in effect
|
||||||
resolved_link.active = self.view == current_view
|
resolved_link.active = self.view == current_view
|
||||||
@@ -257,7 +283,9 @@ class SourceColumn(object):
|
|||||||
|
|
||||||
def __init__(self, source, label, attribute):
|
def __init__(self, source, label, attribute):
|
||||||
self.__class__._registry.setdefault(source, [])
|
self.__class__._registry.setdefault(source, [])
|
||||||
self.__class__._registry[source].append({'label': label, 'attribute': attribute})
|
self.__class__._registry[source].append(
|
||||||
|
{'label': label, 'attribute': attribute}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CombinedSource(object):
|
class CombinedSource(object):
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ class OCRApp(MayanAppConfig):
|
|||||||
links=[link_document_submit], sources=[Document]
|
links=[link_document_submit], sources=[Document]
|
||||||
)
|
)
|
||||||
menu_object.bind_links(
|
menu_object.bind_links(
|
||||||
links=[link_entry_re_queue, link_entry_delete],\
|
links=[link_entry_re_queue, link_entry_delete],
|
||||||
sources=[DocumentVersionOCRError]
|
sources=[DocumentVersionOCRError]
|
||||||
)
|
)
|
||||||
menu_object.bind_links(
|
menu_object.bind_links(
|
||||||
@@ -121,7 +121,10 @@ class OCRApp(MayanAppConfig):
|
|||||||
)
|
)
|
||||||
menu_secondary.bind_links(
|
menu_secondary.bind_links(
|
||||||
links=[link_entry_list],
|
links=[link_entry_list],
|
||||||
sources=['ocr:entry_list', 'ocr:entry_delete_multiple', 'ocr:entry_re_queue_multiple', DocumentVersionOCRError]
|
sources=[
|
||||||
|
'ocr:entry_list', 'ocr:entry_delete_multiple',
|
||||||
|
'ocr:entry_re_queue_multiple', DocumentVersionOCRError
|
||||||
|
]
|
||||||
)
|
)
|
||||||
menu_tools.bind_links(
|
menu_tools.bind_links(
|
||||||
links=[link_document_submit_all, link_entry_list]
|
links=[link_document_submit_all, link_entry_list]
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ class Tesseract(OCRBackendBase):
|
|||||||
# TODO: pass tesseract binary path to the pytesseract
|
# TODO: pass tesseract binary path to the pytesseract
|
||||||
image = Image.open(self.converter.get_page())
|
image = Image.open(self.converter.get_page())
|
||||||
try:
|
try:
|
||||||
result = pytesseract.image_to_string(image=image, lang=self.language)
|
result = pytesseract.image_to_string(
|
||||||
|
image=image, lang=self.language
|
||||||
|
)
|
||||||
# If tesseract gives an error with a language parameter
|
# If tesseract gives an error with a language parameter
|
||||||
# re-run it with no language parameter
|
# re-run it with no language parameter
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ setting_unpaper_path = namespace.add_setting(
|
|||||||
)
|
)
|
||||||
setting_pdftotext_path = namespace.add_setting(
|
setting_pdftotext_path = namespace.add_setting(
|
||||||
global_name='OCR_PDFTOTEXT_PATH', default='/usr/bin/pdftotext',
|
global_name='OCR_PDFTOTEXT_PATH', default='/usr/bin/pdftotext',
|
||||||
help_text=_('File path to poppler\'s pdftotext program used to extract text from PDF files.'),
|
help_text=_(
|
||||||
|
'File path to poppler\'s pdftotext program used to extract text '
|
||||||
|
'from PDF files.'
|
||||||
|
),
|
||||||
is_path=True
|
is_path=True
|
||||||
)
|
)
|
||||||
setting_ocr_backend = namespace.add_setting(
|
setting_ocr_backend = namespace.add_setting(
|
||||||
|
|||||||
@@ -31,16 +31,27 @@ def task_do_ocr(self, document_version_pk):
|
|||||||
document_version = None
|
document_version = None
|
||||||
try:
|
try:
|
||||||
document_version = DocumentVersion.objects.get(pk=document_version_pk)
|
document_version = DocumentVersion.objects.get(pk=document_version_pk)
|
||||||
logger.info('Starting document OCR for document version: %s', document_version)
|
logger.info(
|
||||||
|
'Starting document OCR for document version: %s',
|
||||||
|
document_version
|
||||||
|
)
|
||||||
backend = ocr_backend_class()
|
backend = ocr_backend_class()
|
||||||
backend.process_document_version(document_version)
|
backend.process_document_version(document_version)
|
||||||
except OperationalError as exception:
|
except OperationalError as exception:
|
||||||
logger.warning('OCR error for document version: %s; %s. Retrying.', document_version, exception)
|
logger.warning(
|
||||||
|
'OCR error for document version: %s; %s. Retrying.',
|
||||||
|
document_version, exception
|
||||||
|
)
|
||||||
raise self.retry(exc=exception)
|
raise self.retry(exc=exception)
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
logger.error('OCR error for document version: %s; %s', document_version, exception)
|
logger.error(
|
||||||
|
'OCR error for document version: %s; %s', document_version,
|
||||||
|
exception
|
||||||
|
)
|
||||||
if document_version:
|
if document_version:
|
||||||
entry, created = DocumentVersionOCRError.objects.get_or_create(document_version=document_version)
|
entry, created = DocumentVersionOCRError.objects.get_or_create(
|
||||||
|
document_version=document_version
|
||||||
|
)
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
result = []
|
result = []
|
||||||
@@ -53,15 +64,21 @@ def task_do_ocr(self, document_version_pk):
|
|||||||
|
|
||||||
entry.save()
|
entry.save()
|
||||||
else:
|
else:
|
||||||
logger.info('OCR complete for document version: %s', document_version)
|
logger.info(
|
||||||
|
'OCR complete for document version: %s', document_version
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
entry = DocumentVersionOCRError.objects.get(document_version=document_version)
|
entry = DocumentVersionOCRError.objects.get(
|
||||||
|
document_version=document_version
|
||||||
|
)
|
||||||
except DocumentVersionOCRError.DoesNotExist:
|
except DocumentVersionOCRError.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
entry.delete()
|
entry.delete()
|
||||||
|
|
||||||
post_document_version_ocr.send(sender=self, instance=document_version)
|
post_document_version_ocr.send(
|
||||||
|
sender=self, instance=document_version
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
lock.release()
|
lock.release()
|
||||||
except LockError:
|
except LockError:
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ from documents.test_models import (
|
|||||||
|
|
||||||
class DocumentOCRTestCase(TestCase):
|
class DocumentOCRTestCase(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.document_type = DocumentType.objects.create(label=TEST_DOCUMENT_TYPE)
|
self.document_type = DocumentType.objects.create(
|
||||||
|
label=TEST_DOCUMENT_TYPE
|
||||||
|
)
|
||||||
|
|
||||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
||||||
self.document = self.document_type.new_document(
|
self.document = self.document_type.new_document(
|
||||||
@@ -38,7 +40,9 @@ class GermanOCRSupportTestCase(TestCase):
|
|||||||
|
|
||||||
# Get corresponding language code for German from the default language
|
# Get corresponding language code for German from the default language
|
||||||
# choices list
|
# choices list
|
||||||
language_code = [language for language in setting_language_choices.value if language[1] == 'German'][0][0]
|
language_code = [
|
||||||
|
language for language in setting_language_choices.value if language[1] == 'German'
|
||||||
|
][0][0]
|
||||||
|
|
||||||
self.assertEqual('deu', language_code)
|
self.assertEqual('deu', language_code)
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class DocumentSubmitView(ConfirmView):
|
|||||||
document.submit_for_ocr()
|
document.submit_for_ocr()
|
||||||
messages.success(
|
messages.success(
|
||||||
request,
|
request,
|
||||||
_('Document: %(document)s was added to the OCR queue.') % {
|
_('Document: %(document)s was added to the OCR queue.') % {
|
||||||
'document': document
|
'document': document
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -158,7 +158,11 @@ def entry_delete(request, pk=None, pk_list=None):
|
|||||||
if pk:
|
if pk:
|
||||||
entries = [get_object_or_404(DocumentVersionOCRError, pk=pk)]
|
entries = [get_object_or_404(DocumentVersionOCRError, pk=pk)]
|
||||||
elif pk_list:
|
elif pk_list:
|
||||||
entries = [get_object_or_404(DocumentVersionOCRError, pk=pk) for pk in pk_list.split(',')]
|
entries = [
|
||||||
|
get_object_or_404(
|
||||||
|
DocumentVersionOCRError, pk=pk
|
||||||
|
) for pk in pk_list.split(',')
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
messages.error(request, _('Make at least one selection.'))
|
messages.error(request, _('Make at least one selection.'))
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ class PermissionNamespace(object):
|
|||||||
return cls._registry[name]
|
return cls._registry[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise InvalidNamespace(
|
raise InvalidNamespace(
|
||||||
'Invalid namespace name. This is probably an obsolete permission namespace, execute the management command "purge_permissions" and try again.'
|
'Invalid namespace name. This is probably an obsolete '
|
||||||
|
'permission namespace, execute the management command '
|
||||||
|
'"purge_permissions" and try again.'
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, name, label):
|
def __init__(self, name, label):
|
||||||
@@ -106,5 +108,7 @@ class Permission(object):
|
|||||||
name=self.name,
|
name=self.name,
|
||||||
)
|
)
|
||||||
stored_permission.volatile_permission = self
|
stored_permission.volatile_permission = self
|
||||||
self.__class__._stored_permissions_cache[self.uuid] = stored_permission
|
self.__class__._stored_permissions_cache[
|
||||||
|
self.uuid
|
||||||
|
] = stored_permission
|
||||||
return stored_permission
|
return stored_permission
|
||||||
|
|||||||
@@ -171,7 +171,9 @@ def tag_delete(request, tag_id=None, tag_id_list=None):
|
|||||||
tags = [get_object_or_404(Tag, pk=tag_id)]
|
tags = [get_object_or_404(Tag, pk=tag_id)]
|
||||||
post_action_redirect = reverse('tags:tag_list')
|
post_action_redirect = reverse('tags:tag_list')
|
||||||
elif tag_id_list:
|
elif tag_id_list:
|
||||||
tags = [get_object_or_404(Tag, pk=tag_id) for tag_id in tag_id_list.split(',')]
|
tags = [
|
||||||
|
get_object_or_404(Tag, pk=tag_id) for tag_id in tag_id_list.split(',')
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
messages.error(request, _('Must provide at least one tag.'))
|
messages.error(request, _('Must provide at least one tag.'))
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
@@ -274,7 +276,9 @@ class DocumentTagListView(TagListView):
|
|||||||
permission_document_view, request.user, self.document
|
permission_document_view, request.user, self.document
|
||||||
)
|
)
|
||||||
|
|
||||||
return super(DocumentTagListView, self).dispatch(request, *args, **kwargs)
|
return super(
|
||||||
|
DocumentTagListView, self
|
||||||
|
).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_extra_context(self):
|
def get_extra_context(self):
|
||||||
return {
|
return {
|
||||||
@@ -301,7 +305,9 @@ def tag_remove(request, document_id=None, document_id_list=None, tag_id=None, ta
|
|||||||
if document_id:
|
if document_id:
|
||||||
documents = [get_object_or_404(Document, pk=document_id)]
|
documents = [get_object_or_404(Document, pk=document_id)]
|
||||||
elif document_id_list:
|
elif document_id_list:
|
||||||
documents = [get_object_or_404(Document, pk=document_id) for document_id in document_id_list.split(',')]
|
documents = [
|
||||||
|
get_object_or_404(Document, pk=document_id) for document_id in document_id_list.split(',')
|
||||||
|
]
|
||||||
else:
|
else:
|
||||||
messages.error(
|
messages.error(
|
||||||
request, _('Must provide at least one tagged document.')
|
request, _('Must provide at least one tagged document.')
|
||||||
@@ -400,10 +406,11 @@ def tag_remove(request, document_id=None, document_id_list=None, tag_id=None, ta
|
|||||||
for tag in tags:
|
for tag in tags:
|
||||||
if tag not in document.tags.all():
|
if tag not in document.tags.all():
|
||||||
messages.warning(
|
messages.warning(
|
||||||
request, _('Document "%(document)s" wasn\'t tagged as "%(tag)s"'
|
request, _(
|
||||||
) % {
|
'Document "%(document)s" wasn\'t tagged as "%(tag)s"'
|
||||||
'document': document, 'tag': tag
|
) % {
|
||||||
}
|
'document': document, 'tag': tag
|
||||||
|
}
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
tag.documents.remove(document)
|
tag.documents.remove(document)
|
||||||
@@ -417,8 +424,9 @@ def tag_remove(request, document_id=None, document_id_list=None, tag_id=None, ta
|
|||||||
|
|
||||||
return HttpResponseRedirect(next)
|
return HttpResponseRedirect(next)
|
||||||
else:
|
else:
|
||||||
return render_to_response(template, context,
|
return render_to_response(
|
||||||
context_instance=RequestContext(request))
|
template, context, context_instance=RequestContext(request)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def single_document_multiple_tag_remove(request, document_id):
|
def single_document_multiple_tag_remove(request, document_id):
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ urlpatterns = patterns(
|
|||||||
url(
|
url(
|
||||||
r'^user/multiple/delete/$', 'user_multiple_delete',
|
r'^user/multiple/delete/$', 'user_multiple_delete',
|
||||||
name='user_multiple_delete'
|
name='user_multiple_delete'
|
||||||
),
|
),
|
||||||
url(
|
url(
|
||||||
r'^user/(?P<user_id>\d+)/set_password/$', 'user_set_password',
|
r'^user/(?P<user_id>\d+)/set_password/$', 'user_set_password',
|
||||||
name='user_set_password'
|
name='user_set_password'
|
||||||
|
|||||||
Reference in New Issue
Block a user