PEP8 cleanups.
This commit is contained in:
@@ -19,4 +19,6 @@ class ACLsApp(MayanAppConfig):
|
||||
links=[link_acl_permissions, link_acl_delete],
|
||||
sources=[AccessControlList]
|
||||
)
|
||||
menu_sidebar.bind_links(links=[link_acl_new], sources=['acls:acl_list'])
|
||||
menu_sidebar.bind_links(
|
||||
links=[link_acl_new], sources=['acls:acl_list']
|
||||
)
|
||||
|
||||
@@ -11,7 +11,11 @@ from .permissions import permission_acl_view, permission_acl_edit
|
||||
def get_kwargs_factory(variable_name):
|
||||
def get_kwargs(context):
|
||||
content_type = ContentType.objects.get_for_model(context[variable_name])
|
||||
return {'app_label': '"{}"'.format(content_type.app_label), 'model': '"{}"'.format(content_type.model), 'object_id': '{}.pk'.format(variable_name)}
|
||||
return {
|
||||
'app_label': '"{}"'.format(content_type.app_label),
|
||||
'model': '"{}"'.format(content_type.model),
|
||||
'object_id': '{}.pk'.format(variable_name)
|
||||
}
|
||||
|
||||
return get_kwargs
|
||||
|
||||
|
||||
@@ -92,7 +92,13 @@ class AccessControlListManager(models.Manager):
|
||||
content_type=parent_content_type, role__in=user_roles,
|
||||
permissions=permission.stored_permission
|
||||
)
|
||||
parent_acl_query = Q(**{'{}__pk__in'.format(parent_accessor): parent_queryset.values_list('object_id', flat=True)})
|
||||
parent_acl_query = Q(
|
||||
**{
|
||||
'{}__pk__in'.format(
|
||||
parent_accessor
|
||||
): parent_queryset.values_list('object_id', flat=True)
|
||||
}
|
||||
)
|
||||
else:
|
||||
parent_acl_query = Q()
|
||||
|
||||
|
||||
@@ -15,12 +15,34 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='AccessEntry',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('holder_id', models.PositiveIntegerField()),
|
||||
(
|
||||
'id', models.AutoField(
|
||||
verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True
|
||||
)
|
||||
),
|
||||
(
|
||||
'holder_id', models.PositiveIntegerField()
|
||||
),
|
||||
('object_id', models.PositiveIntegerField()),
|
||||
('content_type', models.ForeignKey(related_name='object_content_type', to='contenttypes.ContentType')),
|
||||
('holder_type', models.ForeignKey(related_name='access_holder', to='contenttypes.ContentType')),
|
||||
('permission', models.ForeignKey(verbose_name='Permission', to='permissions.StoredPermission')),
|
||||
(
|
||||
'content_type', models.ForeignKey(
|
||||
related_name='object_content_type',
|
||||
to='contenttypes.ContentType'
|
||||
)
|
||||
),
|
||||
(
|
||||
'holder_type', models.ForeignKey(
|
||||
related_name='access_holder',
|
||||
to='contenttypes.ContentType'
|
||||
)
|
||||
),
|
||||
(
|
||||
'permission', models.ForeignKey(
|
||||
verbose_name='Permission',
|
||||
to='permissions.StoredPermission'
|
||||
)
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Access entry',
|
||||
@@ -31,7 +53,12 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='CreatorSingleton',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id', models.AutoField(
|
||||
verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True
|
||||
)
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Creator',
|
||||
@@ -42,11 +69,31 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='DefaultAccessEntry',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id', models.AutoField(
|
||||
verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True
|
||||
)
|
||||
),
|
||||
('holder_id', models.PositiveIntegerField()),
|
||||
('content_type', models.ForeignKey(related_name='default_access_entry_class', to='contenttypes.ContentType')),
|
||||
('holder_type', models.ForeignKey(related_name='default_access_entry_holder', to='contenttypes.ContentType')),
|
||||
('permission', models.ForeignKey(verbose_name='Permission', to='permissions.StoredPermission')),
|
||||
(
|
||||
'content_type', models.ForeignKey(
|
||||
related_name='default_access_entry_class',
|
||||
to='contenttypes.ContentType'
|
||||
)
|
||||
),
|
||||
(
|
||||
'holder_type', models.ForeignKey(
|
||||
related_name='default_access_entry_holder',
|
||||
to='contenttypes.ContentType'
|
||||
)
|
||||
),
|
||||
(
|
||||
'permission', models.ForeignKey(
|
||||
verbose_name='Permission',
|
||||
to='permissions.StoredPermission'
|
||||
)
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Default access entry',
|
||||
|
||||
@@ -16,11 +16,31 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='AccessControlList',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
(
|
||||
'id', models.AutoField(
|
||||
verbose_name='ID', serialize=False, auto_created=True,
|
||||
primary_key=True
|
||||
)
|
||||
),
|
||||
('object_id', models.PositiveIntegerField()),
|
||||
('content_type', models.ForeignKey(related_name='object_content_type', to='contenttypes.ContentType')),
|
||||
('permissions', models.ManyToManyField(related_name='acls', verbose_name='Permissions', to='permissions.StoredPermission', blank=True)),
|
||||
('role', models.ForeignKey(related_name='acls', verbose_name='Role', to='permissions.Role')),
|
||||
(
|
||||
'content_type', models.ForeignKey(
|
||||
related_name='object_content_type',
|
||||
to='contenttypes.ContentType'
|
||||
)
|
||||
),
|
||||
(
|
||||
'permissions', models.ManyToManyField(
|
||||
related_name='acls', verbose_name='Permissions',
|
||||
to='permissions.StoredPermission', blank=True
|
||||
)
|
||||
),
|
||||
(
|
||||
'role', models.ForeignKey(
|
||||
related_name='acls', verbose_name='Role',
|
||||
to='permissions.Role'
|
||||
)
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Access entry',
|
||||
|
||||
@@ -31,7 +31,10 @@ class AccessControlList(models.Model):
|
||||
fk_field='object_id',
|
||||
)
|
||||
# TODO: limit choices to the permissions valid for the content_object
|
||||
permissions = models.ManyToManyField(StoredPermission, blank=True, related_name='acls', verbose_name=_('Permissions'))
|
||||
permissions = models.ManyToManyField(
|
||||
StoredPermission, blank=True, related_name='acls',
|
||||
verbose_name=_('Permissions')
|
||||
)
|
||||
role = models.ForeignKey(Role, related_name='acls', verbose_name=_('Role'))
|
||||
|
||||
objects = AccessControlListManager()
|
||||
@@ -45,4 +48,6 @@ class AccessControlList(models.Model):
|
||||
return '{} <=> {}'.format(self.content_object, self.role)
|
||||
|
||||
def get_inherited_permissions(self):
|
||||
return AccessControlList.objects.get_inherited_permissions(role=self.role, obj=self.content_object)
|
||||
return AccessControlList.objects.get_inherited_permissions(
|
||||
role=self.role, obj=self.content_object
|
||||
)
|
||||
|
||||
@@ -6,5 +6,9 @@ from permissions import PermissionNamespace
|
||||
|
||||
namespace = PermissionNamespace('acls', _('Access control lists'))
|
||||
|
||||
permission_acl_edit = namespace.add_permission(name='acl_edit', label=_('Edit ACLs'))
|
||||
permission_acl_view = namespace.add_permission(name='acl_view', label=_('View ACLs'))
|
||||
permission_acl_edit = namespace.add_permission(
|
||||
name='acl_edit', label=_('Edit ACLs')
|
||||
)
|
||||
permission_acl_view = namespace.add_permission(
|
||||
name='acl_view', label=_('View ACLs')
|
||||
)
|
||||
|
||||
@@ -17,26 +17,36 @@ from .models import AccessControlList
|
||||
|
||||
class PermissionTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.document_type_1 = DocumentType.objects.create(label=TEST_DOCUMENT_TYPE)
|
||||
self.document_type_1 = DocumentType.objects.create(
|
||||
label=TEST_DOCUMENT_TYPE
|
||||
)
|
||||
|
||||
ocr_settings = self.document_type_1.ocr_settings
|
||||
ocr_settings.auto_ocr = False
|
||||
ocr_settings.save()
|
||||
|
||||
self.document_type_2 = DocumentType.objects.create(label=TEST_DOCUMENT_TYPE + '2')
|
||||
self.document_type_2 = DocumentType.objects.create(
|
||||
label=TEST_DOCUMENT_TYPE + '2'
|
||||
)
|
||||
|
||||
ocr_settings = self.document_type_2.ocr_settings
|
||||
ocr_settings.auto_ocr = False
|
||||
ocr_settings.save()
|
||||
|
||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
||||
self.document_1 = self.document_type_1.new_document(file_object=File(file_object), label='document 1')
|
||||
self.document_1 = self.document_type_1.new_document(
|
||||
file_object=File(file_object), label='document 1'
|
||||
)
|
||||
|
||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
||||
self.document_2 = self.document_type_1.new_document(file_object=File(file_object), label='document 2')
|
||||
self.document_2 = self.document_type_1.new_document(
|
||||
file_object=File(file_object), label='document 2'
|
||||
)
|
||||
|
||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
||||
self.document_3 = self.document_type_2.new_document(file_object=File(file_object), label='document 3')
|
||||
self.document_3 = self.document_type_2.new_document(
|
||||
file_object=File(file_object), label='document 3'
|
||||
)
|
||||
|
||||
self.user = get_user_model().objects.create(username='test user')
|
||||
self.group = Group.objects.create(name='test group')
|
||||
@@ -52,23 +62,35 @@ class PermissionTestCase(TestCase):
|
||||
|
||||
def test_check_access_without_permissions(self):
|
||||
with self.assertRaises(PermissionDenied):
|
||||
AccessControlList.objects.check_access(permissions=(permission_document_view,), user=self.user, obj=self.document_1)
|
||||
AccessControlList.objects.check_access(
|
||||
permissions=(permission_document_view,),
|
||||
user=self.user, obj=self.document_1
|
||||
)
|
||||
|
||||
def test_filtering_without_permissions(self):
|
||||
self.assertEqual(
|
||||
list(AccessControlList.objects.filter_by_access(permission=permission_document_view, user=self.user, queryset=Document.objects.all())),
|
||||
[]
|
||||
list(
|
||||
AccessControlList.objects.filter_by_access(
|
||||
permission=permission_document_view, user=self.user,
|
||||
queryset=Document.objects.all()
|
||||
)
|
||||
), []
|
||||
)
|
||||
|
||||
def test_check_access_with_acl(self):
|
||||
self.group.user_set.add(self.user)
|
||||
self.role.groups.add(self.group)
|
||||
|
||||
acl = AccessControlList.objects.create(content_object=self.document_1, role=self.role)
|
||||
acl = AccessControlList.objects.create(
|
||||
content_object=self.document_1, role=self.role
|
||||
)
|
||||
acl.permissions.add(permission_document_view.stored_permission)
|
||||
|
||||
try:
|
||||
AccessControlList.objects.check_access(permissions=(permission_document_view,), user=self.user, obj=self.document_1)
|
||||
AccessControlList.objects.check_access(
|
||||
permissions=(permission_document_view,), user=self.user,
|
||||
obj=self.document_1
|
||||
)
|
||||
except PermissionDenied:
|
||||
self.fail('PermissionDenied exception was not expected.')
|
||||
|
||||
@@ -77,23 +99,34 @@ class PermissionTestCase(TestCase):
|
||||
self.role.permissions.add(permission_document_view.stored_permission)
|
||||
self.role.groups.add(self.group)
|
||||
|
||||
acl = AccessControlList.objects.create(content_object=self.document_1, role=self.role)
|
||||
acl = AccessControlList.objects.create(
|
||||
content_object=self.document_1, role=self.role
|
||||
)
|
||||
acl.permissions.add(permission_document_view.stored_permission)
|
||||
|
||||
self.assertEqual(
|
||||
list(AccessControlList.objects.filter_by_access(permission=permission_document_view, user=self.user, queryset=Document.objects.all())),
|
||||
[self.document_1]
|
||||
list(
|
||||
AccessControlList.objects.filter_by_access(
|
||||
permission=permission_document_view, user=self.user,
|
||||
queryset=Document.objects.all()
|
||||
)
|
||||
), [self.document_1]
|
||||
)
|
||||
|
||||
def test_check_access_with_inherited_acl(self):
|
||||
self.group.user_set.add(self.user)
|
||||
self.role.groups.add(self.group)
|
||||
|
||||
acl = AccessControlList.objects.create(content_object=self.document_type_1, role=self.role)
|
||||
acl = AccessControlList.objects.create(
|
||||
content_object=self.document_type_1, role=self.role
|
||||
)
|
||||
acl.permissions.add(permission_document_view.stored_permission)
|
||||
|
||||
try:
|
||||
AccessControlList.objects.check_access(permissions=(permission_document_view,), user=self.user, obj=self.document_1)
|
||||
AccessControlList.objects.check_access(
|
||||
permissions=(permission_document_view,), user=self.user,
|
||||
obj=self.document_1
|
||||
)
|
||||
except PermissionDenied:
|
||||
self.fail('PermissionDenied exception was not expected.')
|
||||
|
||||
@@ -101,14 +134,21 @@ class PermissionTestCase(TestCase):
|
||||
self.group.user_set.add(self.user)
|
||||
self.role.groups.add(self.group)
|
||||
|
||||
acl = AccessControlList.objects.create(content_object=self.document_type_1, role=self.role)
|
||||
acl = AccessControlList.objects.create(
|
||||
content_object=self.document_type_1, role=self.role
|
||||
)
|
||||
acl.permissions.add(permission_document_view.stored_permission)
|
||||
|
||||
acl = AccessControlList.objects.create(content_object=self.document_3, role=self.role)
|
||||
acl = AccessControlList.objects.create(
|
||||
content_object=self.document_3, role=self.role
|
||||
)
|
||||
acl.permissions.add(permission_document_view.stored_permission)
|
||||
|
||||
try:
|
||||
AccessControlList.objects.check_access(permissions=(permission_document_view,), user=self.user, obj=self.document_3)
|
||||
AccessControlList.objects.check_access(
|
||||
permissions=(permission_document_view,), user=self.user,
|
||||
obj=self.document_3
|
||||
)
|
||||
except PermissionDenied:
|
||||
self.fail('PermissionDenied exception was not expected.')
|
||||
|
||||
@@ -117,10 +157,15 @@ class PermissionTestCase(TestCase):
|
||||
self.role.permissions.add(permission_document_view.stored_permission)
|
||||
self.role.groups.add(self.group)
|
||||
|
||||
acl = AccessControlList.objects.create(content_object=self.document_type_1, role=self.role)
|
||||
acl = AccessControlList.objects.create(
|
||||
content_object=self.document_type_1, role=self.role
|
||||
)
|
||||
acl.permissions.add(permission_document_view.stored_permission)
|
||||
|
||||
result = AccessControlList.objects.filter_by_access(permission=permission_document_view, user=self.user, queryset=Document.objects.all())
|
||||
result = AccessControlList.objects.filter_by_access(
|
||||
permission=permission_document_view, user=self.user,
|
||||
queryset=Document.objects.all()
|
||||
)
|
||||
self.assertTrue(self.document_1 in result)
|
||||
self.assertTrue(self.document_2 in result)
|
||||
self.assertTrue(self.document_3 not in result)
|
||||
@@ -130,13 +175,20 @@ class PermissionTestCase(TestCase):
|
||||
self.role.permissions.add(permission_document_view.stored_permission)
|
||||
self.role.groups.add(self.group)
|
||||
|
||||
acl = AccessControlList.objects.create(content_object=self.document_type_1, role=self.role)
|
||||
acl = AccessControlList.objects.create(
|
||||
content_object=self.document_type_1, role=self.role
|
||||
)
|
||||
acl.permissions.add(permission_document_view.stored_permission)
|
||||
|
||||
acl = AccessControlList.objects.create(content_object=self.document_3, role=self.role)
|
||||
acl = AccessControlList.objects.create(
|
||||
content_object=self.document_3, role=self.role
|
||||
)
|
||||
acl.permissions.add(permission_document_view.stored_permission)
|
||||
|
||||
result = AccessControlList.objects.filter_by_access(permission=permission_document_view, user=self.user, queryset=Document.objects.all())
|
||||
result = AccessControlList.objects.filter_by_access(
|
||||
permission=permission_document_view, user=self.user,
|
||||
queryset=Document.objects.all()
|
||||
)
|
||||
self.assertTrue(self.document_1 in result)
|
||||
self.assertTrue(self.document_2 in result)
|
||||
self.assertTrue(self.document_3 in result)
|
||||
|
||||
@@ -6,8 +6,17 @@ from .views import ACLCreateView, ACLDeleteView, ACLListView, ACLPermissionsView
|
||||
|
||||
urlpatterns = patterns(
|
||||
'acls.views',
|
||||
url(r'^(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/new/$', ACLCreateView.as_view(), name='acl_new'),
|
||||
url(r'^(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/list/$', ACLListView.as_view(), name='acl_list'),
|
||||
url(
|
||||
r'^(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/new/$',
|
||||
ACLCreateView.as_view(), name='acl_new'
|
||||
),
|
||||
url(
|
||||
r'^(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/list/$',
|
||||
ACLListView.as_view(), name='acl_list'
|
||||
),
|
||||
url(r'^(?P<pk>\d+)/delete/$', ACLDeleteView.as_view(), name='acl_delete'),
|
||||
url(r'^(?P<pk>\d+)/permissions/$', ACLPermissionsView.as_view(), name='acl_permissions'),
|
||||
url(
|
||||
r'^(?P<pk>\d+)/permissions/$', ACLPermissionsView.as_view(),
|
||||
name='acl_permissions'
|
||||
),
|
||||
)
|
||||
|
||||
@@ -28,25 +28,38 @@ logger = logging.getLogger(__name__)
|
||||
class ACLListView(SingleObjectListView):
|
||||
@staticmethod
|
||||
def permission_titles(permission_list):
|
||||
return ', '.join([unicode(permission) for permission in permission_list])
|
||||
return ', '.join(
|
||||
[unicode(permission) for permission in permission_list]
|
||||
)
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.content_type = get_object_or_404(ContentType, app_label=self.kwargs['app_label'], model=self.kwargs['model'])
|
||||
self.content_type = get_object_or_404(
|
||||
ContentType, app_label=self.kwargs['app_label'],
|
||||
model=self.kwargs['model']
|
||||
)
|
||||
|
||||
try:
|
||||
self.content_object = self.content_type.get_object_for_this_type(pk=self.kwargs['object_id'])
|
||||
self.content_object = self.content_type.get_object_for_this_type(
|
||||
pk=self.kwargs['object_id']
|
||||
)
|
||||
except self.content_type.model_class().DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, permissions=(permission_acl_view,))
|
||||
Permission.check_permissions(
|
||||
request.user, permissions=(permission_acl_view,)
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_acl_view, request.user, self.content_object)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_acl_view, request.user, self.content_object
|
||||
)
|
||||
|
||||
return super(ACLListView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return AccessControlList.objects.filter(content_type=self.content_type, object_id=self.content_object.pk)
|
||||
return AccessControlList.objects.filter(
|
||||
content_type=self.content_type, object_id=self.content_object.pk
|
||||
)
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
@@ -60,7 +73,11 @@ class ACLListView(SingleObjectListView):
|
||||
},
|
||||
{
|
||||
'name': _('Permissions'),
|
||||
'attribute': encapsulate(lambda entry: ACLListView.permission_titles(entry.permissions.all()))
|
||||
'attribute': encapsulate(
|
||||
lambda entry: ACLListView.permission_titles(
|
||||
entry.permissions.all()
|
||||
)
|
||||
)
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -71,17 +88,26 @@ class ACLCreateView(SingleObjectCreateView):
|
||||
model = AccessControlList
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
content_type = get_object_or_404(ContentType, app_label=self.kwargs['app_label'], model=self.kwargs['model'])
|
||||
content_type = get_object_or_404(
|
||||
ContentType, app_label=self.kwargs['app_label'],
|
||||
model=self.kwargs['model']
|
||||
)
|
||||
|
||||
try:
|
||||
self.content_object = content_type.get_object_for_this_type(pk=self.kwargs['object_id'])
|
||||
self.content_object = content_type.get_object_for_this_type(
|
||||
pk=self.kwargs['object_id']
|
||||
)
|
||||
except content_type.model_class().DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, permissions=(permission_acl_edit,))
|
||||
Permission.check_permissions(
|
||||
request.user, permissions=(permission_acl_edit,)
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_acl_edit, request.user, self.content_object)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_acl_edit, request.user, self.content_object
|
||||
)
|
||||
|
||||
return super(ACLCreateView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
@@ -98,7 +124,9 @@ class ACLCreateView(SingleObjectCreateView):
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'object': self.content_object,
|
||||
'title': _('New access control lists for: %s' % self.content_object),
|
||||
'title': _(
|
||||
'New access control lists for: %s' % self.content_object
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -109,9 +137,13 @@ class ACLDeleteView(SingleObjectDeleteView):
|
||||
acl = get_object_or_404(AccessControlList, pk=self.kwargs['pk'])
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, permissions=(permission_acl_edit,))
|
||||
Permission.check_permissions(
|
||||
request.user, permissions=(permission_acl_edit,)
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_acl_edit, request.user, acl.content_object)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_acl_edit, request.user, acl.content_object
|
||||
)
|
||||
|
||||
return super(ACLDeleteView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
@@ -136,8 +168,12 @@ class ACLPermissionsView(AssignRemoveView):
|
||||
results = []
|
||||
|
||||
for namespace, permissions in itertools.groupby(entries, lambda entry: entry.namespace):
|
||||
permission_options = [(unicode(permission.pk), permission) for permission in permissions]
|
||||
results.append((PermissionNamespace.get(namespace), permission_options))
|
||||
permission_options = [
|
||||
(unicode(permission.pk), permission) for permission in permissions
|
||||
]
|
||||
results.append(
|
||||
(PermissionNamespace.get(namespace), permission_options)
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
@@ -149,15 +185,23 @@ class ACLPermissionsView(AssignRemoveView):
|
||||
acl = get_object_or_404(AccessControlList, pk=self.kwargs['pk'])
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, permissions=(permission_acl_edit,))
|
||||
Permission.check_permissions(
|
||||
request.user, permissions=(permission_acl_edit,)
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_acl_edit, request.user, acl.content_object)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_acl_edit, request.user, acl.content_object
|
||||
)
|
||||
|
||||
return super(ACLPermissionsView, self).dispatch(request, *args, **kwargs)
|
||||
return super(
|
||||
ACLPermissionsView, self
|
||||
).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_right_list_help_text(self):
|
||||
if self.get_object().get_inherited_permissions():
|
||||
return _('Disabled permissions are inherited from a parent object.')
|
||||
return _(
|
||||
'Disabled permissions are inherited from a parent object.'
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
@@ -165,14 +209,24 @@ class ACLPermissionsView(AssignRemoveView):
|
||||
return get_object_or_404(AccessControlList, pk=self.kwargs['pk'])
|
||||
|
||||
def get_available_list(self):
|
||||
return ModelPermission.get_for_instance(instance=self.get_object().content_object).exclude(id__in=self.get_granted_list().values_list('pk', flat=True))
|
||||
return ModelPermission.get_for_instance(
|
||||
instance=self.get_object().content_object
|
||||
).exclude(id__in=self.get_granted_list().values_list('pk', flat=True))
|
||||
|
||||
def get_disabled_choices(self):
|
||||
"""
|
||||
Get permissions from a parent's acls but remove the permissions we
|
||||
already hold for this object
|
||||
"""
|
||||
return map(str, set(self.get_object().get_inherited_permissions().values_list('pk', flat=True)).difference(self.get_object().permissions.values_list('pk', flat=True)))
|
||||
return map(
|
||||
str, set(
|
||||
self.get_object().get_inherited_permissions().values_list(
|
||||
'pk', flat=True
|
||||
)
|
||||
).difference(
|
||||
self.get_object().permissions.values_list('pk', flat=True)
|
||||
)
|
||||
)
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace = Namespace(name='authentication', label=_('Authentication'))
|
||||
setting_login_method = namespace.add_setting(
|
||||
global_name='AUTHENTICATION_LOGIN_METHOD', default='username',
|
||||
help_text=_(
|
||||
'Controls the mechanism used to authenticated user. Options are: username, email'
|
||||
'Controls the mechanism used to authenticated user. Options are: '
|
||||
'username, email'
|
||||
)
|
||||
)
|
||||
|
||||
@@ -140,12 +140,12 @@ def checkout_info(request, document_pk):
|
||||
|
||||
return render_to_response(
|
||||
'appearance/generic_template.html', {
|
||||
'paragraphs': paragraphs,
|
||||
'object': document,
|
||||
'title': _('Check out details for document: %s') % document
|
||||
},
|
||||
context_instance=RequestContext(request)
|
||||
)
|
||||
'paragraphs': paragraphs,
|
||||
'object': document,
|
||||
'title': _('Check out details for document: %s') % document
|
||||
},
|
||||
context_instance=RequestContext(request)
|
||||
)
|
||||
|
||||
|
||||
def checkin_document(request, document_pk):
|
||||
|
||||
@@ -300,8 +300,10 @@ class TransformationZoom(BaseTransformation):
|
||||
|
||||
decimal_value = float(self.percent) / 100
|
||||
return self.image.resize(
|
||||
(int(self.image.size[0] * decimal_value),
|
||||
int(self.image.size[1] * decimal_value)), Image.ANTIALIAS
|
||||
(
|
||||
int(self.image.size[0] * decimal_value),
|
||||
int(self.image.size[1] * decimal_value)
|
||||
), Image.ANTIALIAS
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -69,10 +69,12 @@ class TransformationDeleteView(SingleObjectDeleteView):
|
||||
]
|
||||
),
|
||||
'title': _(
|
||||
'Delete transformation "%(transformation)s" for: %(content_object)s?') % {
|
||||
'transformation': self.transformation,
|
||||
'content_object': self.transformation.content_object
|
||||
},
|
||||
'Delete transformation "%(transformation)s" for: '
|
||||
'%(content_object)s?'
|
||||
) % {
|
||||
'transformation': self.transformation,
|
||||
'content_object': self.transformation.content_object
|
||||
},
|
||||
'transformation': self.transformation,
|
||||
}
|
||||
|
||||
|
||||
@@ -29,12 +29,19 @@ def comment_delete(request, comment_id=None, comment_id_list=None):
|
||||
if comment_id:
|
||||
comments = [get_object_or_404(Comment, pk=comment_id)]
|
||||
elif comment_id_list:
|
||||
comments = [get_object_or_404(Comment, pk=comment_id) for comment_id in comment_id_list.split(',')]
|
||||
comments = [
|
||||
get_object_or_404(
|
||||
Comment, pk=comment_id
|
||||
) for comment_id in comment_id_list.split(',')
|
||||
]
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_comment_delete])
|
||||
except PermissionDenied:
|
||||
comments = AccessControlList.objects.filter_by_access(permission_comment_delete, request.user, comments, related='content_object')
|
||||
comments = AccessControlList.objects.filter_by_access(
|
||||
permission_comment_delete, request.user, comments,
|
||||
related='content_object'
|
||||
)
|
||||
|
||||
if not comments:
|
||||
messages.error(request, _('Must provide at least one comment.'))
|
||||
@@ -47,11 +54,17 @@ def comment_delete(request, comment_id=None, comment_id_list=None):
|
||||
for comment in comments:
|
||||
try:
|
||||
comment.delete()
|
||||
messages.success(request, _('Comment "%s" deleted successfully.') % comment)
|
||||
messages.success(
|
||||
request, _('Comment "%s" deleted successfully.') % comment
|
||||
)
|
||||
except Exception as exception:
|
||||
messages.error(request, _('Error deleting comment "%(comment)s": %(error)s') % {
|
||||
'comment': comment, 'error': exception
|
||||
})
|
||||
messages.error(
|
||||
request, _(
|
||||
'Error deleting comment "%(comment)s": %(error)s'
|
||||
) % {
|
||||
'comment': comment, 'error': exception
|
||||
}
|
||||
)
|
||||
|
||||
return HttpResponseRedirect(next)
|
||||
|
||||
|
||||
@@ -45,9 +45,13 @@ def document_version_post_save_hook(instance):
|
||||
logger.debug('instance: %s', instance)
|
||||
|
||||
try:
|
||||
document_signature = DocumentVersionSignature.objects.get(document_version=instance)
|
||||
document_signature = DocumentVersionSignature.objects.get(
|
||||
document_version=instance
|
||||
)
|
||||
except DocumentVersionSignature.DoesNotExist:
|
||||
document_signature = DocumentVersionSignature.objects.create(document_version=instance)
|
||||
document_signature = DocumentVersionSignature.objects.create(
|
||||
document_version=instance
|
||||
)
|
||||
document_signature.check_for_embedded_signature()
|
||||
|
||||
|
||||
@@ -60,7 +64,9 @@ class DocumentSignaturesApp(MayanAppConfig):
|
||||
def ready(self):
|
||||
super(DocumentSignaturesApp, self).ready()
|
||||
|
||||
DocumentVersion.register_post_save_hook(1, document_version_post_save_hook)
|
||||
DocumentVersion.register_post_save_hook(
|
||||
1, document_version_post_save_hook
|
||||
)
|
||||
DocumentVersion.register_pre_open_hook(1, document_pre_open_hook)
|
||||
|
||||
ModelPermission.register(
|
||||
@@ -70,5 +76,18 @@ class DocumentSignaturesApp(MayanAppConfig):
|
||||
)
|
||||
)
|
||||
|
||||
menu_facet.bind_links(links=[link_document_verify], sources=[Document])
|
||||
menu_sidebar.bind_links(links=[link_document_signature_upload, link_document_signature_download, link_document_signature_delete], sources=['signatures:document_verify', 'signatures:document_signature_upload', 'signatures:document_signature_download', 'signatures:document_signature_delete'])
|
||||
menu_facet.bind_links(
|
||||
links=[link_document_verify], sources=[Document]
|
||||
)
|
||||
menu_sidebar.bind_links(
|
||||
links=[
|
||||
link_document_signature_upload,
|
||||
link_document_signature_download,
|
||||
link_document_signature_delete
|
||||
], sources=[
|
||||
'signatures:document_verify',
|
||||
'signatures:document_signature_upload',
|
||||
'signatures:document_signature_download',
|
||||
'signatures:document_signature_delete'
|
||||
]
|
||||
)
|
||||
|
||||
@@ -12,14 +12,36 @@ from .permissions import (
|
||||
|
||||
|
||||
def can_upload_detached_signature(context):
|
||||
return not DocumentVersionSignature.objects.has_detached_signature(context['object'].latest_version) and not DocumentVersionSignature.objects.has_embedded_signature(context['object'].latest_version)
|
||||
return not DocumentVersionSignature.objects.has_detached_signature(
|
||||
context['object'].latest_version
|
||||
) and not DocumentVersionSignature.objects.has_embedded_signature(
|
||||
context['object'].latest_version
|
||||
)
|
||||
|
||||
|
||||
def can_delete_detached_signature(context):
|
||||
return DocumentVersionSignature.objects.has_detached_signature(context['object'].latest_version)
|
||||
return DocumentVersionSignature.objects.has_detached_signature(
|
||||
context['object'].latest_version
|
||||
)
|
||||
|
||||
|
||||
link_document_signature_delete = Link(condition=can_delete_detached_signature, permissions=[permission_signature_delete], tags='dangerous', text=_('Delete signature'), view='signatures:document_signature_delete', args='object.pk')
|
||||
link_document_signature_download = Link(condition=can_delete_detached_signature, text=_('Download signature'), view='signatures:document_signature_download', args='object.pk', permissions=[permission_signature_download])
|
||||
link_document_signature_upload = Link(condition=can_upload_detached_signature, permissions=[permission_signature_upload], text=_('Upload signature'), view='signatures:document_signature_upload', args='object.pk')
|
||||
link_document_verify = Link(permissions=[permission_document_verify], text=_('Signatures'), view='signatures:document_verify', args='object.pk')
|
||||
link_document_signature_delete = Link(
|
||||
condition=can_delete_detached_signature,
|
||||
permissions=[permission_signature_delete], tags='dangerous',
|
||||
text=_('Delete signature'), view='signatures:document_signature_delete',
|
||||
args='object.pk'
|
||||
)
|
||||
link_document_signature_download = Link(
|
||||
condition=can_delete_detached_signature, text=_('Download signature'),
|
||||
view='signatures:document_signature_download', args='object.pk',
|
||||
permissions=[permission_signature_download]
|
||||
)
|
||||
link_document_signature_upload = Link(
|
||||
condition=can_upload_detached_signature,
|
||||
permissions=[permission_signature_upload], text=_('Upload signature'),
|
||||
view='signatures:document_signature_upload', args='object.pk'
|
||||
)
|
||||
link_document_verify = Link(
|
||||
permissions=[permission_document_verify], text=_('Signatures'),
|
||||
view='signatures:document_verify', args='object.pk'
|
||||
)
|
||||
|
||||
@@ -24,7 +24,9 @@ class DocumentVersionSignatureManager(models.Manager):
|
||||
)
|
||||
|
||||
if document_signature.has_embedded_signature:
|
||||
raise Exception('Document version already has an embedded signature')
|
||||
raise Exception(
|
||||
'Document version already has an embedded signature'
|
||||
)
|
||||
else:
|
||||
if document_signature.signature_file:
|
||||
logger.debug('Existing detached signature')
|
||||
@@ -37,7 +39,9 @@ class DocumentVersionSignatureManager(models.Manager):
|
||||
|
||||
def has_detached_signature(self, document_version):
|
||||
try:
|
||||
document_signature = self.get_document_signature(document_version=document_version)
|
||||
document_signature = self.get_document_signature(
|
||||
document_version=document_version
|
||||
)
|
||||
except ValueError:
|
||||
return False
|
||||
else:
|
||||
@@ -50,23 +54,31 @@ class DocumentVersionSignatureManager(models.Manager):
|
||||
logger.debug('document_version: %s', document_version)
|
||||
|
||||
try:
|
||||
document_signature = self.get_document_signature(document_version=document_version)
|
||||
document_signature = self.get_document_signature(
|
||||
document_version=document_version
|
||||
)
|
||||
except ValueError:
|
||||
return False
|
||||
else:
|
||||
return document_signature.has_embedded_signature
|
||||
|
||||
def detached_signature(self, document_version):
|
||||
document_signature = self.get_document_signature(document_version=document_version)
|
||||
document_signature = self.get_document_signature(
|
||||
document_version=document_version
|
||||
)
|
||||
|
||||
return document_signature.signature_file.storage.open(document_signature.signature_file.name)
|
||||
return document_signature.signature_file.storage.open(
|
||||
document_signature.signature_file.name
|
||||
)
|
||||
|
||||
def verify_signature(self, document_version):
|
||||
document_version_descriptor = document_version.open(raw=True)
|
||||
detached_signature = None
|
||||
if self.has_detached_signature(document_version=document_version):
|
||||
logger.debug('has detached signature')
|
||||
detached_signature = self.detached_signature(document_version=document_version)
|
||||
detached_signature = self.detached_signature(
|
||||
document_version=document_version
|
||||
)
|
||||
args = (document_version_descriptor, detached_signature)
|
||||
else:
|
||||
args = (document_version_descriptor,)
|
||||
@@ -81,7 +93,9 @@ class DocumentVersionSignatureManager(models.Manager):
|
||||
detached_signature.close()
|
||||
|
||||
def clear_detached_signature(self, document_version):
|
||||
document_signature = self.get_document_signature(document_version=document_version)
|
||||
document_signature = self.get_document_signature(
|
||||
document_version=document_version
|
||||
)
|
||||
if not document_signature.signature_file:
|
||||
raise Exception('document doesn\'t have a detached signature')
|
||||
|
||||
|
||||
@@ -23,9 +23,16 @@ class DocumentVersionSignature(models.Model):
|
||||
"""
|
||||
Model that describes a document version signature properties
|
||||
"""
|
||||
document_version = models.ForeignKey(DocumentVersion, editable=False, verbose_name=_('Document version'))
|
||||
signature_file = models.FileField(blank=True, null=True, storage=storage_backend, upload_to=upload_to, verbose_name=_('Signature file'))
|
||||
has_embedded_signature = models.BooleanField(default=False, verbose_name=_('Has embedded signature'))
|
||||
document_version = models.ForeignKey(
|
||||
DocumentVersion, editable=False, verbose_name=_('Document version')
|
||||
)
|
||||
signature_file = models.FileField(
|
||||
blank=True, null=True, storage=storage_backend, upload_to=upload_to,
|
||||
verbose_name=_('Signature file')
|
||||
)
|
||||
has_embedded_signature = models.BooleanField(
|
||||
default=False, verbose_name=_('Has embedded signature')
|
||||
)
|
||||
|
||||
objects = DocumentVersionSignatureManager()
|
||||
|
||||
@@ -33,7 +40,9 @@ class DocumentVersionSignature(models.Model):
|
||||
logger.debug('checking for embedded signature')
|
||||
|
||||
with self.document_version.open(raw=True) as file_object:
|
||||
self.has_embedded_signature = gpg.has_embedded_signature(file_object)
|
||||
self.has_embedded_signature = gpg.has_embedded_signature(
|
||||
file_object
|
||||
)
|
||||
self.save()
|
||||
|
||||
def delete_detached_signature_file(self):
|
||||
|
||||
@@ -4,9 +4,19 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from permissions import PermissionNamespace
|
||||
|
||||
namespace = PermissionNamespace('document_signatures', _('Document signatures'))
|
||||
namespace = PermissionNamespace(
|
||||
'document_signatures', _('Document signatures')
|
||||
)
|
||||
|
||||
permission_document_verify = namespace.add_permission(name='document_verify', label=_('Verify document signatures'))
|
||||
permission_signature_delete = namespace.add_permission(name='signature_delete', label=_('Delete detached signatures'))
|
||||
permission_signature_download = namespace.add_permission(name='signature_download', label=_('Download detached signatures'))
|
||||
permission_signature_upload = namespace.add_permission(name='signature_upload', label=_('Upload detached signatures'))
|
||||
permission_document_verify = namespace.add_permission(
|
||||
name='document_verify', label=_('Verify document signatures')
|
||||
)
|
||||
permission_signature_delete = namespace.add_permission(
|
||||
name='signature_delete', label=_('Delete detached signatures')
|
||||
)
|
||||
permission_signature_download = namespace.add_permission(
|
||||
name='signature_download', label=_('Download detached signatures')
|
||||
)
|
||||
permission_signature_upload = namespace.add_permission(
|
||||
name='signature_upload', label=_('Upload detached signatures')
|
||||
)
|
||||
|
||||
@@ -5,4 +5,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from smart_settings import Namespace
|
||||
|
||||
namespace = Namespace(name='signatures', label=_('Document signatures'))
|
||||
setting_storage_backend = namespace.add_setting(global_name='SIGNATURES_STORAGE_BACKEND', default='storage.backends.filebasedstorage.FileBasedStorage')
|
||||
setting_storage_backend = namespace.add_setting(
|
||||
global_name='SIGNATURES_STORAGE_BACKEND',
|
||||
default='storage.backends.filebasedstorage.FileBasedStorage'
|
||||
)
|
||||
|
||||
@@ -13,21 +13,32 @@ from django_gpg.runtime import gpg
|
||||
|
||||
from .models import DocumentVersionSignature
|
||||
|
||||
TEST_SIGNED_DOCUMENT_PATH = os.path.join(settings.BASE_DIR, 'contrib', 'sample_documents', 'mayan_11_1.pdf.gpg')
|
||||
TEST_SIGNATURE_FILE_PATH = os.path.join(settings.BASE_DIR, 'contrib', 'sample_documents', 'mayan_11_1.pdf.sig')
|
||||
TEST_KEY_FILE = os.path.join(settings.BASE_DIR, 'contrib', 'sample_documents', 'key0x5F3F7F75D210724D.asc')
|
||||
TEST_SIGNED_DOCUMENT_PATH = os.path.join(
|
||||
settings.BASE_DIR, 'contrib', 'sample_documents', 'mayan_11_1.pdf.gpg'
|
||||
)
|
||||
TEST_SIGNATURE_FILE_PATH = os.path.join(
|
||||
settings.BASE_DIR, 'contrib', 'sample_documents', 'mayan_11_1.pdf.sig'
|
||||
)
|
||||
TEST_KEY_FILE = os.path.join(
|
||||
settings.BASE_DIR, 'contrib', 'sample_documents',
|
||||
'key0x5F3F7F75D210724D.asc'
|
||||
)
|
||||
|
||||
|
||||
class DocumentTestCase(TestCase):
|
||||
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.auto_ocr = False
|
||||
ocr_settings.save()
|
||||
|
||||
with open(TEST_DOCUMENT_PATH) as file_object:
|
||||
self.document = self.document_type.new_document(file_object=File(file_object), label='mayan_11_1.pdf')
|
||||
self.document = self.document_type.new_document(
|
||||
file_object=File(file_object), label='mayan_11_1.pdf'
|
||||
)
|
||||
|
||||
with open(TEST_KEY_FILE) as file_object:
|
||||
gpg.import_key(file_object.read())
|
||||
@@ -37,24 +48,52 @@ class DocumentTestCase(TestCase):
|
||||
self.document_type.delete()
|
||||
|
||||
def test_document_no_signature(self):
|
||||
self.assertEqual(DocumentVersionSignature.objects.has_detached_signature(self.document.latest_version), False)
|
||||
self.assertEqual(
|
||||
DocumentVersionSignature.objects.has_detached_signature(
|
||||
self.document.latest_version
|
||||
), False
|
||||
)
|
||||
|
||||
def test_new_document_version_signed(self):
|
||||
with open(TEST_SIGNED_DOCUMENT_PATH) as file_object:
|
||||
self.document.new_version(file_object=File(file_object), comment='test comment 1')
|
||||
self.document.new_version(
|
||||
file_object=File(file_object), comment='test comment 1'
|
||||
)
|
||||
|
||||
self.assertEqual(DocumentVersionSignature.objects.has_detached_signature(self.document.latest_version), False)
|
||||
self.assertEqual(DocumentVersionSignature.objects.verify_signature(self.document.latest_version).status, SIGNATURE_STATE_VALID)
|
||||
self.assertEqual(
|
||||
DocumentVersionSignature.objects.has_detached_signature(
|
||||
self.document.latest_version
|
||||
), False
|
||||
)
|
||||
self.assertEqual(
|
||||
DocumentVersionSignature.objects.verify_signature(
|
||||
self.document.latest_version
|
||||
).status, SIGNATURE_STATE_VALID
|
||||
)
|
||||
|
||||
def test_detached_signatures(self):
|
||||
with open(TEST_DOCUMENT_PATH) as file_object:
|
||||
self.document.new_version(file_object=File(file_object), comment='test comment 2')
|
||||
self.document.new_version(
|
||||
file_object=File(file_object), comment='test comment 2'
|
||||
)
|
||||
|
||||
# GPGVerificationError
|
||||
self.assertEqual(DocumentVersionSignature.objects.verify_signature(self.document.latest_version), None)
|
||||
self.assertEqual(DocumentVersionSignature.objects.verify_signature(
|
||||
self.document.latest_version), None
|
||||
)
|
||||
|
||||
with open(TEST_SIGNATURE_FILE_PATH, 'rb') as file_object:
|
||||
DocumentVersionSignature.objects.add_detached_signature(self.document.latest_version, File(file_object))
|
||||
DocumentVersionSignature.objects.add_detached_signature(
|
||||
self.document.latest_version, File(file_object)
|
||||
)
|
||||
|
||||
self.assertEqual(DocumentVersionSignature.objects.has_detached_signature(self.document.latest_version), True)
|
||||
self.assertEqual(DocumentVersionSignature.objects.verify_signature(self.document.latest_version).status, SIGNATURE_STATE_VALID)
|
||||
self.assertEqual(
|
||||
DocumentVersionSignature.objects.has_detached_signature(
|
||||
self.document.latest_version
|
||||
), True
|
||||
)
|
||||
self.assertEqual(
|
||||
DocumentVersionSignature.objects.verify_signature(
|
||||
self.document.latest_version
|
||||
).status, SIGNATURE_STATE_VALID
|
||||
)
|
||||
|
||||
@@ -37,32 +37,122 @@ class DocumentStatesApp(MayanAppConfig):
|
||||
def ready(self):
|
||||
super(DocumentStatesApp, self).ready()
|
||||
|
||||
SourceColumn(source=Workflow, label=_('Initial state'), attribute=encapsulate(lambda workflow: workflow.get_initial_state() or _('None')))
|
||||
SourceColumn(
|
||||
source=Workflow, label=_('Initial state'),
|
||||
attribute=encapsulate(
|
||||
lambda workflow: workflow.get_initial_state() or _('None')
|
||||
)
|
||||
)
|
||||
|
||||
SourceColumn(source=WorkflowInstance, label=_('Current state'), attribute='get_current_state')
|
||||
SourceColumn(source=WorkflowInstance, label=_('User'), attribute=encapsulate(lambda workflow: getattr(workflow.get_last_log_entry(), 'user', _('None'))))
|
||||
SourceColumn(source=WorkflowInstance, label=_('Last transition'), attribute='get_last_transition')
|
||||
SourceColumn(source=WorkflowInstance, label=_('Date and time'), attribute=encapsulate(lambda workflow: getattr(workflow.get_last_log_entry(), 'datetime', _('None'))))
|
||||
SourceColumn(source=WorkflowInstance, label=_('Completion'), attribute=encapsulate(lambda workflow: getattr(workflow.get_current_state(), 'completion', _('None'))))
|
||||
SourceColumn(
|
||||
source=WorkflowInstance, label=_('Current state'),
|
||||
attribute='get_current_state'
|
||||
)
|
||||
SourceColumn(
|
||||
source=WorkflowInstance, label=_('User'),
|
||||
attribute=encapsulate(
|
||||
lambda workflow: getattr(
|
||||
workflow.get_last_log_entry(), 'user', _('None')
|
||||
)
|
||||
)
|
||||
)
|
||||
SourceColumn(
|
||||
source=WorkflowInstance, label=_('Last transition'),
|
||||
attribute='get_last_transition'
|
||||
)
|
||||
SourceColumn(
|
||||
source=WorkflowInstance, label=_('Date and time'),
|
||||
attribute=encapsulate(
|
||||
lambda workflow: getattr(
|
||||
workflow.get_last_log_entry(), 'datetime', _('None')
|
||||
)
|
||||
)
|
||||
)
|
||||
SourceColumn(
|
||||
source=WorkflowInstance, label=_('Completion'),
|
||||
attribute=encapsulate(lambda workflow: getattr(
|
||||
workflow.get_current_state(), 'completion', _('None'))
|
||||
)
|
||||
)
|
||||
|
||||
SourceColumn(source=WorkflowInstanceLogEntry, label=_('Date and time'), attribute='datetime')
|
||||
SourceColumn(source=WorkflowInstanceLogEntry, label=_('User'), attribute='user')
|
||||
SourceColumn(source=WorkflowInstanceLogEntry, label=_('Transition'), attribute='transition')
|
||||
SourceColumn(source=WorkflowInstanceLogEntry, label=_('Comment'), attribute='comment')
|
||||
SourceColumn(
|
||||
source=WorkflowInstanceLogEntry, label=_('Date and time'),
|
||||
attribute='datetime'
|
||||
)
|
||||
SourceColumn(
|
||||
source=WorkflowInstanceLogEntry, label=_('User'), attribute='user'
|
||||
)
|
||||
SourceColumn(
|
||||
source=WorkflowInstanceLogEntry, label=_('Transition'),
|
||||
attribute='transition'
|
||||
)
|
||||
SourceColumn(
|
||||
source=WorkflowInstanceLogEntry, label=_('Comment'),
|
||||
attribute='comment'
|
||||
)
|
||||
|
||||
SourceColumn(source=WorkflowState, label=_('Is initial state?'), attribute=encapsulate(lambda state: two_state_template(state.initial)))
|
||||
SourceColumn(source=WorkflowState, label=_('Completion'), attribute='completion')
|
||||
SourceColumn(
|
||||
source=WorkflowState, label=_('Is initial state?'),
|
||||
attribute=encapsulate(
|
||||
lambda state: two_state_template(state.initial)
|
||||
)
|
||||
)
|
||||
SourceColumn(
|
||||
source=WorkflowState, label=_('Completion'), attribute='completion'
|
||||
)
|
||||
|
||||
SourceColumn(source=WorkflowTransition, label=_('Origin state'), attribute='origin_state')
|
||||
SourceColumn(source=WorkflowTransition, label=_('Destination state'), attribute='destination_state')
|
||||
SourceColumn(
|
||||
source=WorkflowTransition, label=_('Origin state'),
|
||||
attribute='origin_state'
|
||||
)
|
||||
SourceColumn(
|
||||
source=WorkflowTransition, label=_('Destination state'),
|
||||
attribute='destination_state'
|
||||
)
|
||||
|
||||
menu_facet.bind_links(links=[link_document_workflow_instance_list], sources=[Document])
|
||||
menu_object.bind_links(links=[link_setup_workflow_states, link_setup_workflow_transitions, link_setup_workflow_document_types, link_setup_workflow_edit, link_setup_workflow_delete], sources=[Workflow])
|
||||
menu_object.bind_links(links=[link_setup_workflow_state_edit, link_setup_workflow_state_delete], sources=[WorkflowState])
|
||||
menu_object.bind_links(links=[link_setup_workflow_transition_edit, link_setup_workflow_transition_delete], sources=[WorkflowTransition])
|
||||
menu_object.bind_links(links=[link_workflow_instance_detail, link_workflow_instance_transition], sources=[WorkflowInstance])
|
||||
menu_secondary.bind_links(links=[link_setup_workflow_list, link_setup_workflow_create], sources=[Workflow, 'document_states:setup_workflow_create', 'document_states:setup_workflow_list'])
|
||||
menu_facet.bind_links(
|
||||
links=[link_document_workflow_instance_list], sources=[Document]
|
||||
)
|
||||
menu_object.bind_links(
|
||||
links=[
|
||||
link_setup_workflow_states, link_setup_workflow_transitions,
|
||||
link_setup_workflow_document_types, link_setup_workflow_edit,
|
||||
link_setup_workflow_delete
|
||||
], sources=[Workflow]
|
||||
)
|
||||
menu_object.bind_links(
|
||||
links=[
|
||||
link_setup_workflow_state_edit,
|
||||
link_setup_workflow_state_delete
|
||||
], sources=[WorkflowState]
|
||||
)
|
||||
menu_object.bind_links(
|
||||
links=[
|
||||
link_setup_workflow_transition_edit,
|
||||
link_setup_workflow_transition_delete
|
||||
], sources=[WorkflowTransition]
|
||||
)
|
||||
menu_object.bind_links(
|
||||
links=[
|
||||
link_workflow_instance_detail,
|
||||
link_workflow_instance_transition
|
||||
], sources=[WorkflowInstance]
|
||||
)
|
||||
menu_secondary.bind_links(
|
||||
links=[link_setup_workflow_list, link_setup_workflow_create],
|
||||
sources=[
|
||||
Workflow, 'document_states:setup_workflow_create',
|
||||
'document_states:setup_workflow_list'
|
||||
]
|
||||
)
|
||||
menu_setup.bind_links(links=[link_setup_workflow_list])
|
||||
menu_sidebar.bind_links(links=[link_setup_workflow_state_create, link_setup_workflow_transition_create], sources=[Workflow])
|
||||
menu_sidebar.bind_links(
|
||||
links=[
|
||||
link_setup_workflow_state_create,
|
||||
link_setup_workflow_transition_create
|
||||
], sources=[Workflow]
|
||||
)
|
||||
|
||||
post_save.connect(launch_workflow, dispatch_uid='launch_workflow', sender=Document)
|
||||
post_save.connect(
|
||||
launch_workflow, dispatch_uid='launch_workflow', sender=Document
|
||||
)
|
||||
|
||||
@@ -4,19 +4,66 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from navigation import Link
|
||||
|
||||
link_document_workflow_instance_list = Link(text=_('Workflows'), view='document_states:document_workflow_instance_list', args='object.pk')
|
||||
link_setup_workflow_create = Link(text=_('Create workflow'), view='document_states:setup_workflow_create')
|
||||
link_setup_workflow_delete = Link(tags='dangerous', text=_('Delete'), view='document_states:setup_workflow_delete', args='object.pk')
|
||||
link_setup_workflow_document_types = Link(text=_('Document types'), view='document_states:setup_workflow_document_types', args='object.pk')
|
||||
link_setup_workflow_edit = Link(text=_('Edit'), view='document_states:setup_workflow_edit', args='object.pk')
|
||||
link_setup_workflow_list = Link(icon='fa fa-sitemap', text=_('Workflows'), view='document_states:setup_workflow_list')
|
||||
link_setup_workflow_state_create = Link(text=_('Create state'), view='document_states:setup_workflow_state_create', args='object.pk')
|
||||
link_setup_workflow_state_delete = Link(tags='dangerous', text=_('Delete'), view='document_states:setup_workflow_state_delete', args='object.pk')
|
||||
link_setup_workflow_state_edit = Link(text=_('Edit'), view='document_states:setup_workflow_state_edit', args='object.pk')
|
||||
link_setup_workflow_states = Link(text=_('States'), view='document_states:setup_workflow_states', args='object.pk')
|
||||
link_setup_workflow_transition_create = Link(text=_('Create transition'), view='document_states:setup_workflow_transition_create', args='object.pk')
|
||||
link_setup_workflow_transition_delete = Link(tags='dangerous', text=_('Delete'), view='document_states:setup_workflow_transition_delete', args='object.pk')
|
||||
link_setup_workflow_transition_edit = Link(text=_('Edit'), view='document_states:setup_workflow_transition_edit', args='object.pk')
|
||||
link_setup_workflow_transitions = Link(text=_('Transitions'), view='document_states:setup_workflow_transitions', args='object.pk')
|
||||
link_workflow_instance_detail = Link(text=_('Detail'), view='document_states:workflow_instance_detail', args='resolved_object.pk')
|
||||
link_workflow_instance_transition = Link(text=_('Transition'), view='document_states:workflow_instance_transition', args='resolved_object.pk')
|
||||
link_document_workflow_instance_list = Link(
|
||||
text=_('Workflows'),
|
||||
view='document_states:document_workflow_instance_list', args='object.pk'
|
||||
)
|
||||
link_setup_workflow_create = Link(
|
||||
text=_('Create workflow'), view='document_states:setup_workflow_create'
|
||||
)
|
||||
link_setup_workflow_delete = Link(
|
||||
tags='dangerous', text=_('Delete'),
|
||||
view='document_states:setup_workflow_delete', args='object.pk'
|
||||
)
|
||||
link_setup_workflow_document_types = Link(
|
||||
text=_('Document types'),
|
||||
view='document_states:setup_workflow_document_types', args='object.pk'
|
||||
)
|
||||
link_setup_workflow_edit = Link(
|
||||
text=_('Edit'), view='document_states:setup_workflow_edit',
|
||||
args='object.pk'
|
||||
)
|
||||
link_setup_workflow_list = Link(
|
||||
icon='fa fa-sitemap', text=_('Workflows'),
|
||||
view='document_states:setup_workflow_list'
|
||||
)
|
||||
link_setup_workflow_state_create = Link(
|
||||
text=_('Create state'),
|
||||
view='document_states:setup_workflow_state_create', args='object.pk'
|
||||
)
|
||||
link_setup_workflow_state_delete = Link(
|
||||
tags='dangerous', text=_('Delete'),
|
||||
view='document_states:setup_workflow_state_delete', args='object.pk'
|
||||
)
|
||||
link_setup_workflow_state_edit = Link(
|
||||
text=_('Edit'), view='document_states:setup_workflow_state_edit',
|
||||
args='object.pk'
|
||||
)
|
||||
link_setup_workflow_states = Link(
|
||||
text=_('States'), view='document_states:setup_workflow_states',
|
||||
args='object.pk'
|
||||
)
|
||||
link_setup_workflow_transition_create = Link(
|
||||
text=_('Create transition'),
|
||||
view='document_states:setup_workflow_transition_create', args='object.pk'
|
||||
)
|
||||
link_setup_workflow_transition_delete = Link(
|
||||
tags='dangerous', text=_('Delete'),
|
||||
view='document_states:setup_workflow_transition_delete', args='object.pk'
|
||||
)
|
||||
link_setup_workflow_transition_edit = Link(
|
||||
text=_('Edit'), view='document_states:setup_workflow_transition_edit',
|
||||
args='object.pk'
|
||||
)
|
||||
link_setup_workflow_transitions = Link(
|
||||
text=_('Transitions'), view='document_states:setup_workflow_transitions',
|
||||
args='object.pk'
|
||||
)
|
||||
link_workflow_instance_detail = Link(
|
||||
text=_('Detail'), view='document_states:workflow_instance_detail',
|
||||
args='resolved_object.pk'
|
||||
)
|
||||
link_workflow_instance_transition = Link(
|
||||
text=_('Transition'),
|
||||
view='document_states:workflow_instance_transition', args='resolved_object.pk'
|
||||
)
|
||||
|
||||
@@ -17,8 +17,13 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Workflow(models.Model):
|
||||
label = models.CharField(max_length=255, unique=True, verbose_name=_('Label'))
|
||||
document_types = models.ManyToManyField(DocumentType, related_name='workflows', verbose_name=_('Document types'))
|
||||
label = models.CharField(
|
||||
max_length=255, unique=True, verbose_name=_('Label')
|
||||
)
|
||||
document_types = models.ManyToManyField(
|
||||
DocumentType, related_name='workflows',
|
||||
verbose_name=_('Document types')
|
||||
)
|
||||
|
||||
objects = WorkflowManager()
|
||||
|
||||
@@ -36,12 +41,18 @@ class Workflow(models.Model):
|
||||
|
||||
def launch_for(self, document):
|
||||
try:
|
||||
logger.info('Launching workflow %s for document %s', self, document)
|
||||
logger.info(
|
||||
'Launching workflow %s for document %s', self, document
|
||||
)
|
||||
self.instances.create(document=document)
|
||||
except IntegrityError:
|
||||
logger.info('Workflow %s already launched for document %s', self, document)
|
||||
logger.info(
|
||||
'Workflow %s already launched for document %s', self, document
|
||||
)
|
||||
else:
|
||||
logger.info('Workflow %s launched for document %s', self, document)
|
||||
logger.info(
|
||||
'Workflow %s launched for document %s', self, document
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Workflow')
|
||||
@@ -50,10 +61,23 @@ class Workflow(models.Model):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class WorkflowState(models.Model):
|
||||
workflow = models.ForeignKey(Workflow, related_name='states', verbose_name=_('Workflow'))
|
||||
workflow = models.ForeignKey(
|
||||
Workflow, related_name='states', verbose_name=_('Workflow')
|
||||
)
|
||||
label = models.CharField(max_length=255, verbose_name=_('Label'))
|
||||
initial = models.BooleanField(default=False, help_text=_('Select if this will be the state with which you want the workflow to start in. Only one state can be the initial state.'), verbose_name=_('Initial'))
|
||||
completion = models.IntegerField(blank=True, default=0, help_text=_('Enter the percent of completion that this state represents in relation to the workflow. Use numbers without the percent sign.'), verbose_name=_('Completion'))
|
||||
initial = models.BooleanField(
|
||||
default=False,
|
||||
help_text=_(
|
||||
'Select if this will be the state with which you want the '
|
||||
'workflow to start in. Only one state can be the initial state.'
|
||||
), verbose_name=_('Initial')
|
||||
)
|
||||
completion = models.IntegerField(
|
||||
blank=True, default=0, help_text=_(
|
||||
'Enter the percent of completion that this state represents in '
|
||||
'relation to the workflow. Use numbers without the percent sign.'
|
||||
), verbose_name=_('Completion')
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
@@ -71,36 +95,54 @@ class WorkflowState(models.Model):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class WorkflowTransition(models.Model):
|
||||
workflow = models.ForeignKey(Workflow, related_name='transitions', verbose_name=_('Workflow'))
|
||||
workflow = models.ForeignKey(
|
||||
Workflow, related_name='transitions', verbose_name=_('Workflow')
|
||||
)
|
||||
label = models.CharField(max_length=255, verbose_name=_('Label'))
|
||||
|
||||
origin_state = models.ForeignKey(WorkflowState, related_name='origin_transitions', verbose_name=_('Origin state'))
|
||||
destination_state = models.ForeignKey(WorkflowState, related_name='destination_transitions', verbose_name=_('Destination state'))
|
||||
origin_state = models.ForeignKey(
|
||||
WorkflowState, related_name='origin_transitions',
|
||||
verbose_name=_('Origin state')
|
||||
)
|
||||
destination_state = models.ForeignKey(
|
||||
WorkflowState, related_name='destination_transitions',
|
||||
verbose_name=_('Destination state')
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
|
||||
class Meta:
|
||||
unique_together = ('workflow', 'label', 'origin_state', 'destination_state')
|
||||
unique_together = (
|
||||
'workflow', 'label', 'origin_state', 'destination_state'
|
||||
)
|
||||
verbose_name = _('Workflow transition')
|
||||
verbose_name_plural = _('Workflow transitions')
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class WorkflowInstance(models.Model):
|
||||
workflow = models.ForeignKey(Workflow, related_name='instances', verbose_name=_('Workflow'))
|
||||
document = models.ForeignKey(Document, related_name='workflows', verbose_name=_('Document'))
|
||||
workflow = models.ForeignKey(
|
||||
Workflow, related_name='instances', verbose_name=_('Workflow')
|
||||
)
|
||||
document = models.ForeignKey(
|
||||
Document, related_name='workflows', verbose_name=_('Document')
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return unicode(self.workflow)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('document_states:workflow_instance_detail', args=[str(self.pk)])
|
||||
return reverse(
|
||||
'document_states:workflow_instance_detail', args=[str(self.pk)]
|
||||
)
|
||||
|
||||
def do_transition(self, comment, transition, user):
|
||||
try:
|
||||
if transition in self.get_current_state().origin_transitions.all():
|
||||
self.log_entries.create(comment=comment, transition=transition, user=user)
|
||||
self.log_entries.create(
|
||||
comment=comment, transition=transition, user=user
|
||||
)
|
||||
except AttributeError:
|
||||
# No initial state has been set for this workflow
|
||||
pass
|
||||
@@ -134,9 +176,16 @@ class WorkflowInstance(models.Model):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class WorkflowInstanceLogEntry(models.Model):
|
||||
workflow_instance = models.ForeignKey(WorkflowInstance, related_name='log_entries', verbose_name=_('Workflow instance'))
|
||||
datetime = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name=_('Datetime'))
|
||||
transition = models.ForeignKey(WorkflowTransition, verbose_name=_('Transition'))
|
||||
workflow_instance = models.ForeignKey(
|
||||
WorkflowInstance, related_name='log_entries',
|
||||
verbose_name=_('Workflow instance')
|
||||
)
|
||||
datetime = models.DateTimeField(
|
||||
auto_now_add=True, db_index=True, verbose_name=_('Datetime')
|
||||
)
|
||||
transition = models.ForeignKey(
|
||||
WorkflowTransition, verbose_name=_('Transition')
|
||||
)
|
||||
user = models.ForeignKey(User, verbose_name=_('User'))
|
||||
comment = models.TextField(blank=True, verbose_name=_('Comment'))
|
||||
|
||||
|
||||
@@ -6,9 +6,22 @@ from permissions import PermissionNamespace
|
||||
|
||||
namespace = PermissionNamespace('document_states', _('Document workflows'))
|
||||
|
||||
permission_workflow_create = namespace.add_permission(name='workflow_create', label=_('Create workflows'))
|
||||
permission_workflow_delete = namespace.add_permission(name='workflow_delte', label=_('Delete workflows'))
|
||||
permission_workflow_edit = namespace.add_permission(name='workflow_edit', label=_('Edit workflows'))
|
||||
permission_workflow_view = namespace.add_permission(name='workflow_view', label=_('View workflows'))
|
||||
permission_document_workflow_view = namespace.add_permission(name='document_workflow_view', label=_('View document workflows'))
|
||||
permission_document_workflow_transition = namespace.add_permission(name='document_workflow_transition', label=_('Transition document workflows'))
|
||||
permission_workflow_create = namespace.add_permission(
|
||||
name='workflow_create', label=_('Create workflows')
|
||||
)
|
||||
permission_workflow_delete = namespace.add_permission(
|
||||
name='workflow_delte', label=_('Delete workflows')
|
||||
)
|
||||
permission_workflow_edit = namespace.add_permission(
|
||||
name='workflow_edit', label=_('Edit workflows')
|
||||
)
|
||||
permission_workflow_view = namespace.add_permission(
|
||||
name='workflow_view', label=_('View workflows')
|
||||
)
|
||||
permission_document_workflow_view = namespace.add_permission(
|
||||
name='document_workflow_view', label=_('View document workflows')
|
||||
)
|
||||
permission_document_workflow_transition = namespace.add_permission(
|
||||
name='document_workflow_transition',
|
||||
label=_('Transition document workflows')
|
||||
)
|
||||
|
||||
@@ -16,22 +16,84 @@ from .views import (
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^document/(?P<pk>\d+)/workflows/$', DocumentWorkflowInstanceListView.as_view(), name='document_workflow_instance_list'),
|
||||
url(r'^document/workflows/(?P<pk>\d+)/$', WorkflowInstanceDetailView.as_view(), name='workflow_instance_detail'),
|
||||
url(r'^document/workflows/(?P<pk>\d+)/transition/$', WorkflowInstanceTransitionView.as_view(), name='workflow_instance_transition'),
|
||||
url(
|
||||
r'^document/(?P<pk>\d+)/workflows/$',
|
||||
DocumentWorkflowInstanceListView.as_view(),
|
||||
name='document_workflow_instance_list'
|
||||
),
|
||||
url(
|
||||
r'^document/workflows/(?P<pk>\d+)/$',
|
||||
WorkflowInstanceDetailView.as_view(), name='workflow_instance_detail'
|
||||
),
|
||||
url(
|
||||
r'^document/workflows/(?P<pk>\d+)/transition/$',
|
||||
WorkflowInstanceTransitionView.as_view(),
|
||||
name='workflow_instance_transition'
|
||||
),
|
||||
|
||||
url(r'^setup/all/$', SetupWorkflowListView.as_view(), name='setup_workflow_list'),
|
||||
url(r'^setup/create/$', SetupWorkflowCreateView.as_view(), name='setup_workflow_create'),
|
||||
url(r'^setup/(?P<pk>\d+)/edit/$', SetupWorkflowEditView.as_view(), name='setup_workflow_edit'),
|
||||
url(r'^setup/(?P<pk>\d+)/delete/$', SetupWorkflowDeleteView.as_view(), name='setup_workflow_delete'),
|
||||
url(r'^setup/(?P<pk>\d+)/documents/$', WorkflowDocumentListView.as_view(), name='setup_workflow_document_list'),
|
||||
url(r'^setup/(?P<pk>\d+)/document_types/$', SetupWorkflowDocumentTypesView.as_view(), name='setup_workflow_document_types'),
|
||||
url(r'^setup/(?P<pk>\d+)/states/$', SetupWorkflowStateListView.as_view(), name='setup_workflow_states'),
|
||||
url(r'^setup/(?P<pk>\d+)/states/create/$', SetupWorkflowStateCreateView.as_view(), name='setup_workflow_state_create'),
|
||||
url(r'^setup/(?P<pk>\d+)/transitions/$', SetupWorkflowTransitionListView.as_view(), name='setup_workflow_transitions'),
|
||||
url(r'^setup/(?P<pk>\d+)/transitions/create/$', SetupWorkflowTransitionCreateView.as_view(), name='setup_workflow_transition_create'),
|
||||
url(r'^setup/workflow/state/(?P<pk>\d+)/delete/$', SetupWorkflowStateDeleteView.as_view(), name='setup_workflow_state_delete'),
|
||||
url(r'^setup/workflow/state/(?P<pk>\d+)/edit/$', SetupWorkflowStateEditView.as_view(), name='setup_workflow_state_edit'),
|
||||
url(r'^setup/workflow/transitions/(?P<pk>\d+)/delete/$', SetupWorkflowTransitionDeleteView.as_view(), name='setup_workflow_transition_delete'),
|
||||
url(r'^setup/workflow/transitions/(?P<pk>\d+)/edit/$', SetupWorkflowTransitionEditView.as_view(), name='setup_workflow_transition_edit'),
|
||||
url(
|
||||
r'^setup/all/$', SetupWorkflowListView.as_view(),
|
||||
name='setup_workflow_list'
|
||||
),
|
||||
url(
|
||||
r'^setup/create/$', SetupWorkflowCreateView.as_view(),
|
||||
name='setup_workflow_create'
|
||||
),
|
||||
url(
|
||||
r'^setup/(?P<pk>\d+)/edit/$', SetupWorkflowEditView.as_view(),
|
||||
name='setup_workflow_edit'
|
||||
),
|
||||
url(
|
||||
r'^setup/(?P<pk>\d+)/delete/$', SetupWorkflowDeleteView.as_view(),
|
||||
name='setup_workflow_delete'
|
||||
),
|
||||
url(
|
||||
r'^setup/(?P<pk>\d+)/documents/$',
|
||||
WorkflowDocumentListView.as_view(),
|
||||
name='setup_workflow_document_list'
|
||||
),
|
||||
url(
|
||||
r'^setup/(?P<pk>\d+)/document_types/$',
|
||||
SetupWorkflowDocumentTypesView.as_view(),
|
||||
name='setup_workflow_document_types'
|
||||
),
|
||||
url(
|
||||
r'^setup/(?P<pk>\d+)/states/$', SetupWorkflowStateListView.as_view(),
|
||||
name='setup_workflow_states'
|
||||
),
|
||||
url(
|
||||
r'^setup/(?P<pk>\d+)/states/create/$',
|
||||
SetupWorkflowStateCreateView.as_view(),
|
||||
name='setup_workflow_state_create'
|
||||
),
|
||||
url(
|
||||
r'^setup/(?P<pk>\d+)/transitions/$',
|
||||
SetupWorkflowTransitionListView.as_view(),
|
||||
name='setup_workflow_transitions'
|
||||
),
|
||||
url(
|
||||
r'^setup/(?P<pk>\d+)/transitions/create/$',
|
||||
SetupWorkflowTransitionCreateView.as_view(),
|
||||
name='setup_workflow_transition_create'
|
||||
),
|
||||
url(
|
||||
r'^setup/workflow/state/(?P<pk>\d+)/delete/$',
|
||||
SetupWorkflowStateDeleteView.as_view(),
|
||||
name='setup_workflow_state_delete'
|
||||
),
|
||||
url(
|
||||
r'^setup/workflow/state/(?P<pk>\d+)/edit/$',
|
||||
SetupWorkflowStateEditView.as_view(),
|
||||
name='setup_workflow_state_edit'
|
||||
),
|
||||
url(
|
||||
r'^setup/workflow/transitions/(?P<pk>\d+)/delete/$',
|
||||
SetupWorkflowTransitionDeleteView.as_view(),
|
||||
name='setup_workflow_transition_delete'
|
||||
),
|
||||
url(
|
||||
r'^setup/workflow/transitions/(?P<pk>\d+)/edit/$',
|
||||
SetupWorkflowTransitionEditView.as_view(),
|
||||
name='setup_workflow_transition_edit'
|
||||
),
|
||||
)
|
||||
|
||||
@@ -33,11 +33,18 @@ from .permissions import (
|
||||
class DocumentWorkflowInstanceListView(SingleObjectListView):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_document_workflow_view])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_document_workflow_view]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_document_workflow_view, request.user, self.get_document())
|
||||
AccessControlList.objects.check_access(
|
||||
permission_document_workflow_view, request.user,
|
||||
self.get_document()
|
||||
)
|
||||
|
||||
return super(DocumentWorkflowInstanceListView, self).dispatch(request, *args, **kwargs)
|
||||
return super(
|
||||
DocumentWorkflowInstanceListView, self
|
||||
).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_document(self):
|
||||
return get_object_or_404(Document, pk=self.kwargs['pk'])
|
||||
@@ -46,12 +53,16 @@ class DocumentWorkflowInstanceListView(SingleObjectListView):
|
||||
return self.get_document().workflows.all()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DocumentWorkflowInstanceListView, self).get_context_data(**kwargs)
|
||||
context = super(
|
||||
DocumentWorkflowInstanceListView, self
|
||||
).get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'hide_link': True,
|
||||
'object': self.get_document(),
|
||||
'title': _('Workflows for document: %s') % self.get_document(),
|
||||
'title': _(
|
||||
'Workflows for document: %s'
|
||||
) % self.get_document(),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -63,14 +74,22 @@ class WorkflowDocumentListView(DocumentListView):
|
||||
self.workflow = get_object_or_404(Workflow, pk=self.kwargs['pk'])
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_workflow_view])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_workflow_view]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_workflow_view, request.user, self.workflow)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_workflow_view, request.user, self.workflow
|
||||
)
|
||||
|
||||
return super(WorkflowDocumentListView, self).dispatch(request, *args, **kwargs)
|
||||
return super(
|
||||
WorkflowDocumentListView, self
|
||||
).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_document_queryset(self):
|
||||
return Document.objects.filter(document_type__in=self.workflow.document_types.all())
|
||||
return Document.objects.filter(
|
||||
document_type__in=self.workflow.document_types.all()
|
||||
)
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
@@ -83,11 +102,18 @@ class WorkflowDocumentListView(DocumentListView):
|
||||
class WorkflowInstanceDetailView(SingleObjectListView):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_document_workflow_view])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_document_workflow_view]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_document_workflow_view, request.user, self.get_workflow_instance().document)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_document_workflow_view, request.user,
|
||||
self.get_workflow_instance().document
|
||||
)
|
||||
|
||||
return super(WorkflowInstanceDetailView, self).dispatch(request, *args, **kwargs)
|
||||
return super(
|
||||
WorkflowInstanceDetailView, self
|
||||
).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_workflow_instance(self):
|
||||
return get_object_or_404(WorkflowInstance, pk=self.kwargs['pk'])
|
||||
@@ -116,15 +142,27 @@ class WorkflowInstanceTransitionView(FormView):
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_document_workflow_transition])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_document_workflow_transition]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_document_workflow_transition, request.user, self.get_workflow_instance().document)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_document_workflow_transition, request.user,
|
||||
self.get_workflow_instance().document
|
||||
)
|
||||
|
||||
return super(WorkflowInstanceTransitionView, self).dispatch(request, *args, **kwargs)
|
||||
return super(
|
||||
WorkflowInstanceTransitionView, self
|
||||
).dispatch(request, *args, **kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
transition = self.get_workflow_instance().workflow.transitions.get(pk=form.cleaned_data['transition'])
|
||||
self.get_workflow_instance().do_transition(comment=form.cleaned_data['comment'], transition=transition, user=self.request.user)
|
||||
transition = self.get_workflow_instance().workflow.transitions.get(
|
||||
pk=form.cleaned_data['transition']
|
||||
)
|
||||
self.get_workflow_instance().do_transition(
|
||||
comment=form.cleaned_data['comment'], transition=transition,
|
||||
user=self.request.user
|
||||
)
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
def get_form_kwargs(self):
|
||||
@@ -136,14 +174,18 @@ class WorkflowInstanceTransitionView(FormView):
|
||||
return get_object_or_404(WorkflowInstance, pk=self.kwargs['pk'])
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(WorkflowInstanceTransitionView, self).get_context_data(**kwargs)
|
||||
context = super(
|
||||
WorkflowInstanceTransitionView, self
|
||||
).get_context_data(**kwargs)
|
||||
|
||||
context.update(
|
||||
{
|
||||
'navigation_object_list': ['object', 'workflow_instance'],
|
||||
'object': self.get_workflow_instance().document,
|
||||
'submit_label': _('Submit'),
|
||||
'title': _('Do transition for workflow: %s') % self.get_workflow_instance(),
|
||||
'title': _(
|
||||
'Do transition for workflow: %s'
|
||||
) % self.get_workflow_instance(),
|
||||
'workflow_instance': self.get_workflow_instance(),
|
||||
}
|
||||
)
|
||||
@@ -193,17 +235,21 @@ class SetupWorkflowDocumentTypesView(AssignRemoveView):
|
||||
|
||||
def add(self, item):
|
||||
self.get_object().document_types.add(item)
|
||||
# TODO: add task launching this workflow for all the document types of
|
||||
# item
|
||||
# TODO: add task launching this workflow for all the document types
|
||||
# of item
|
||||
|
||||
def get_object(self):
|
||||
return get_object_or_404(Workflow, pk=self.kwargs['pk'])
|
||||
|
||||
def left_list(self):
|
||||
return AssignRemoveView.generate_choices(self.get_object().get_document_types_not_in_workflow())
|
||||
return AssignRemoveView.generate_choices(
|
||||
self.get_object().get_document_types_not_in_workflow()
|
||||
)
|
||||
|
||||
def right_list(self):
|
||||
return AssignRemoveView.generate_choices(self.get_object().document_types.all())
|
||||
return AssignRemoveView.generate_choices(
|
||||
self.get_object().document_types.all()
|
||||
)
|
||||
|
||||
def remove(self, item):
|
||||
self.get_object().document_types.remove(item)
|
||||
@@ -211,9 +257,13 @@ class SetupWorkflowDocumentTypesView(AssignRemoveView):
|
||||
# item
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
data = super(SetupWorkflowDocumentTypesView, self).get_context_data(**kwargs)
|
||||
data = super(
|
||||
SetupWorkflowDocumentTypesView, self
|
||||
).get_context_data(**kwargs)
|
||||
data.update({
|
||||
'title': _('Document types assigned the workflow: %s') % self.get_object(),
|
||||
'title': _(
|
||||
'Document types assigned the workflow: %s'
|
||||
) % self.get_object(),
|
||||
'object': self.get_object(),
|
||||
})
|
||||
|
||||
@@ -223,11 +273,17 @@ class SetupWorkflowDocumentTypesView(AssignRemoveView):
|
||||
class SetupWorkflowStateListView(SingleObjectListView):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_workflow_edit])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_workflow_edit]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_workflow_edit, request.user, self.get_workflow())
|
||||
AccessControlList.objects.check_access(
|
||||
permission_workflow_edit, request.user, self.get_workflow()
|
||||
)
|
||||
|
||||
return super(SetupWorkflowStateListView, self).dispatch(request, *args, **kwargs)
|
||||
return super(
|
||||
SetupWorkflowStateListView, self
|
||||
).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_workflow(self):
|
||||
return get_object_or_404(Workflow, pk=self.kwargs['pk'])
|
||||
@@ -236,7 +292,9 @@ class SetupWorkflowStateListView(SingleObjectListView):
|
||||
return self.get_workflow().states.all()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(SetupWorkflowStateListView, self).get_context_data(**kwargs)
|
||||
context = super(
|
||||
SetupWorkflowStateListView, self
|
||||
).get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'hide_link': True,
|
||||
@@ -253,18 +311,28 @@ class SetupWorkflowStateCreateView(SingleObjectCreateView):
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_workflow_edit])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_workflow_edit]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_workflow_edit, request.user, self.get_workflow())
|
||||
AccessControlList.objects.check_access(
|
||||
permission_workflow_edit, request.user, self.get_workflow()
|
||||
)
|
||||
|
||||
return super(SetupWorkflowStateCreateView, self).dispatch(request, *args, **kwargs)
|
||||
return super(
|
||||
SetupWorkflowStateCreateView, self
|
||||
).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(SetupWorkflowStateCreateView, self).get_context_data(**kwargs)
|
||||
context = super(
|
||||
SetupWorkflowStateCreateView, self
|
||||
).get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'object': self.get_workflow(),
|
||||
'title': _('Create states for workflow: %s') % self.get_workflow()
|
||||
'title': _(
|
||||
'Create states for workflow: %s'
|
||||
) % self.get_workflow()
|
||||
}
|
||||
)
|
||||
return context
|
||||
@@ -276,7 +344,9 @@ class SetupWorkflowStateCreateView(SingleObjectCreateView):
|
||||
return self.get_workflow().states.all()
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('document_states:setup_workflow_states', args=[self.kwargs['pk']])
|
||||
return reverse(
|
||||
'document_states:setup_workflow_states', args=[self.kwargs['pk']]
|
||||
)
|
||||
|
||||
def form_valid(self, form):
|
||||
self.object = form.save(commit=False)
|
||||
@@ -290,7 +360,9 @@ class SetupWorkflowStateDeleteView(SingleObjectDeleteView):
|
||||
view_permission = permission_workflow_delete
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(SetupWorkflowStateDeleteView, self).get_context_data(**kwargs)
|
||||
context = super(
|
||||
SetupWorkflowStateDeleteView, self
|
||||
).get_context_data(**kwargs)
|
||||
|
||||
context.update(
|
||||
{
|
||||
@@ -303,7 +375,10 @@ class SetupWorkflowStateDeleteView(SingleObjectDeleteView):
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('document_states:setup_workflow_States', args=[self.get_object().workflow.pk])
|
||||
return reverse(
|
||||
'document_states:setup_workflow_States',
|
||||
args=[self.get_object().workflow.pk]
|
||||
)
|
||||
|
||||
|
||||
class SetupWorkflowStateEditView(SingleObjectEditView):
|
||||
@@ -312,7 +387,9 @@ class SetupWorkflowStateEditView(SingleObjectEditView):
|
||||
view_permission = permission_workflow_edit
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(SetupWorkflowStateEditView, self).get_context_data(**kwargs)
|
||||
context = super(
|
||||
SetupWorkflowStateEditView, self
|
||||
).get_context_data(**kwargs)
|
||||
|
||||
context.update(
|
||||
{
|
||||
@@ -325,7 +402,10 @@ class SetupWorkflowStateEditView(SingleObjectEditView):
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('document_states:setup_workflow_states', args=[self.get_object().workflow.pk])
|
||||
return reverse(
|
||||
'document_states:setup_workflow_states',
|
||||
args=[self.get_object().workflow.pk]
|
||||
)
|
||||
|
||||
|
||||
# Transitions
|
||||
@@ -334,11 +414,17 @@ class SetupWorkflowStateEditView(SingleObjectEditView):
|
||||
class SetupWorkflowTransitionListView(SingleObjectListView):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_workflow_edit])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_workflow_edit]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_workflow_edit, request.user, self.get_workflow())
|
||||
AccessControlList.objects.check_access(
|
||||
permission_workflow_edit, request.user, self.get_workflow()
|
||||
)
|
||||
|
||||
return super(SetupWorkflowTransitionListView, self).dispatch(request, *args, **kwargs)
|
||||
return super(
|
||||
SetupWorkflowTransitionListView, self
|
||||
).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_workflow(self):
|
||||
return get_object_or_404(Workflow, pk=self.kwargs['pk'])
|
||||
@@ -347,12 +433,16 @@ class SetupWorkflowTransitionListView(SingleObjectListView):
|
||||
return self.get_workflow().transitions.all()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(SetupWorkflowTransitionListView, self).get_context_data(**kwargs)
|
||||
context = super(
|
||||
SetupWorkflowTransitionListView, self
|
||||
).get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'hide_link': True,
|
||||
'object': self.get_workflow(),
|
||||
'title': _('Transitions of workflow: %s') % self.get_workflow()
|
||||
'title': _(
|
||||
'Transitions of workflow: %s'
|
||||
) % self.get_workflow()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -364,24 +454,36 @@ class SetupWorkflowTransitionCreateView(SingleObjectCreateView):
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_workflow_edit])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_workflow_edit]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_workflow_edit, request.user, self.get_workflow())
|
||||
AccessControlList.objects.check_access(
|
||||
permission_workflow_edit, request.user, self.get_workflow()
|
||||
)
|
||||
|
||||
return super(SetupWorkflowTransitionCreateView, self).dispatch(request, *args, **kwargs)
|
||||
return super(
|
||||
SetupWorkflowTransitionCreateView, self
|
||||
).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(SetupWorkflowTransitionCreateView, self).get_context_data(**kwargs)
|
||||
context = super(
|
||||
SetupWorkflowTransitionCreateView, self
|
||||
).get_context_data(**kwargs)
|
||||
context.update(
|
||||
{
|
||||
'object': self.get_workflow(),
|
||||
'title': _('Create transitions for workflow: %s') % self.get_workflow()
|
||||
'title': _(
|
||||
'Create transitions for workflow: %s'
|
||||
) % self.get_workflow()
|
||||
}
|
||||
)
|
||||
return context
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(SetupWorkflowTransitionCreateView, self).get_form_kwargs()
|
||||
kwargs = super(
|
||||
SetupWorkflowTransitionCreateView, self
|
||||
).get_form_kwargs()
|
||||
kwargs['workflow'] = self.get_workflow()
|
||||
return kwargs
|
||||
|
||||
@@ -392,7 +494,10 @@ class SetupWorkflowTransitionCreateView(SingleObjectCreateView):
|
||||
return self.get_workflow().transitions.all()
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('document_states:setup_workflow_transitions', args=[self.kwargs['pk']])
|
||||
return reverse(
|
||||
'document_states:setup_workflow_transitions',
|
||||
args=[self.kwargs['pk']]
|
||||
)
|
||||
|
||||
def form_valid(self, form):
|
||||
self.object = form.save(commit=False)
|
||||
@@ -400,8 +505,12 @@ class SetupWorkflowTransitionCreateView(SingleObjectCreateView):
|
||||
try:
|
||||
self.object.save()
|
||||
except IntegrityError:
|
||||
messages.error(self.request, _('Unable to save transition; integrity error.'))
|
||||
return super(SetupWorkflowTransitionCreateView, self).form_invalid(form)
|
||||
messages.error(
|
||||
self.request, _('Unable to save transition; integrity error.')
|
||||
)
|
||||
return super(
|
||||
SetupWorkflowTransitionCreateView, self
|
||||
).form_invalid(form)
|
||||
else:
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
|
||||
@@ -411,7 +520,9 @@ class SetupWorkflowTransitionDeleteView(SingleObjectDeleteView):
|
||||
view_permission = permission_workflow_delete
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(SetupWorkflowTransitionDeleteView, self).get_context_data(**kwargs)
|
||||
context = super(
|
||||
SetupWorkflowTransitionDeleteView, self
|
||||
).get_context_data(**kwargs)
|
||||
|
||||
context.update(
|
||||
{
|
||||
@@ -424,7 +535,10 @@ class SetupWorkflowTransitionDeleteView(SingleObjectDeleteView):
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('document_states:setup_workflow_transitions', args=[self.get_object().workflow.pk])
|
||||
return reverse(
|
||||
'document_states:setup_workflow_transitions',
|
||||
args=[self.get_object().workflow.pk]
|
||||
)
|
||||
|
||||
|
||||
class SetupWorkflowTransitionEditView(SingleObjectEditView):
|
||||
@@ -433,7 +547,9 @@ class SetupWorkflowTransitionEditView(SingleObjectEditView):
|
||||
view_permission = permission_workflow_edit
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(SetupWorkflowTransitionEditView, self).get_context_data(**kwargs)
|
||||
context = super(
|
||||
SetupWorkflowTransitionEditView, self
|
||||
).get_context_data(**kwargs)
|
||||
|
||||
context.update(
|
||||
{
|
||||
@@ -446,9 +562,14 @@ class SetupWorkflowTransitionEditView(SingleObjectEditView):
|
||||
return context
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(SetupWorkflowTransitionEditView, self).get_form_kwargs()
|
||||
kwargs = super(
|
||||
SetupWorkflowTransitionEditView, self
|
||||
).get_form_kwargs()
|
||||
kwargs['workflow'] = self.get_object().workflow
|
||||
return kwargs
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('document_states:setup_workflow_transitions', args=[self.get_object().workflow.pk])
|
||||
return reverse(
|
||||
'document_states:setup_workflow_transitions',
|
||||
args=[self.get_object().workflow.pk]
|
||||
)
|
||||
|
||||
@@ -162,9 +162,9 @@ class APIDocumentVersionCreateView(generics.CreateAPIView):
|
||||
)
|
||||
|
||||
if serializer.is_valid():
|
||||
# Nested resource we take the document pk from the URL and insert it
|
||||
# so that it needs not to be specified by the user, we mark it as
|
||||
# a read only field in the serializer
|
||||
# Nested resource we take the document pk from the URL and insert
|
||||
# it so that it needs not to be specified by the user, we mark
|
||||
# it as a read only field in the serializer
|
||||
document = get_object_or_404(Document, pk=kwargs['pk'])
|
||||
|
||||
document.new_version(
|
||||
@@ -361,7 +361,8 @@ class APIDocumentTypeDocumentListView(generics.ListAPIView):
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(
|
||||
permission_document_type_view, self.request.user, document_type
|
||||
permission_document_type_view, self.request.user,
|
||||
document_type
|
||||
)
|
||||
|
||||
return document_type.documents.all()
|
||||
|
||||
@@ -84,8 +84,10 @@ class DocumentsApp(MayanAppConfig):
|
||||
|
||||
MissingItem(
|
||||
label=_('Create a document type'),
|
||||
description=_('Every uploaded document must be assigned a document type, it is the basic way Mayan EDMS categorizes documents.'),
|
||||
condition=lambda: not DocumentType.objects.exists(),
|
||||
description=_(
|
||||
'Every uploaded document must be assigned a document type, '
|
||||
'it is the basic way Mayan EDMS categorizes documents.'
|
||||
), condition=lambda: not DocumentType.objects.exists(),
|
||||
view='documents:document_type_list'
|
||||
)
|
||||
|
||||
|
||||
@@ -42,7 +42,9 @@ class DocumentPreviewForm(forms.Form):
|
||||
super(DocumentPreviewForm, self).__init__(*args, **kwargs)
|
||||
self.fields['preview'].initial = document
|
||||
try:
|
||||
self.fields['preview'].label = _('Document pages (%d)') % document.page_count
|
||||
self.fields['preview'].label = _(
|
||||
'Document pages (%d)'
|
||||
) % document.page_count
|
||||
except AttributeError:
|
||||
self.fields['preview'].label = _('Document pages (%d)') % 0
|
||||
|
||||
@@ -62,13 +64,16 @@ class DocumentForm(forms.ModelForm):
|
||||
|
||||
super(DocumentForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# Is a document (documents app edit) and has been saved (sources app upload)?
|
||||
# Is a document (documents app edit) and has been saved (sources
|
||||
# app upload)?
|
||||
if self.instance and self.instance.pk:
|
||||
document_type = self.instance.document_type
|
||||
|
||||
filenames_qs = document_type.filenames.filter(enabled=True)
|
||||
if filenames_qs.count():
|
||||
self.fields['document_type_available_filenames'] = forms.ModelChoiceField(
|
||||
self.fields[
|
||||
'document_type_available_filenames'
|
||||
] = forms.ModelChoiceField(
|
||||
queryset=filenames_qs,
|
||||
required=False,
|
||||
label=_('Quick document rename')
|
||||
@@ -119,12 +124,20 @@ class DocumentTypeFilenameForm_create(forms.ModelForm):
|
||||
class DocumentDownloadForm(forms.Form):
|
||||
compressed = forms.BooleanField(
|
||||
label=_('Compress'), required=False,
|
||||
help_text=_('Download the document in the original format or in a compressed manner. This option is selectable only when downloading one document, for multiple documents, the bundle will always be downloads as a compressed file.')
|
||||
help_text=_(
|
||||
'Download the document in the original format or in a compressed '
|
||||
'manner. This option is selectable only when downloading one '
|
||||
'document, for multiple documents, the bundle will always be '
|
||||
'downloads as a compressed file.'
|
||||
)
|
||||
)
|
||||
zip_filename = forms.CharField(
|
||||
initial=DEFAULT_ZIP_FILENAME, label=_('Compressed filename'),
|
||||
required=False,
|
||||
help_text=_('The filename of the compressed file that will contain the documents to be downloaded, if the previous option is selected.')
|
||||
help_text=_(
|
||||
'The filename of the compressed file that will contain the '
|
||||
'documents to be downloaded, if the previous option is selected.'
|
||||
)
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
@@ -38,67 +38,229 @@ def is_min_zoom(context):
|
||||
|
||||
|
||||
# Facet
|
||||
link_document_preview = Link(permissions=[permission_document_view], text=_('Preview'), view='documents:document_preview', args='object.id')
|
||||
link_document_properties = Link(permissions=[permission_document_view], text=_('Properties'), view='documents:document_properties', args='object.id')
|
||||
link_document_version_list = Link(permissions=[permission_document_view], text=_('Versions'), view='documents:document_version_list', args='object.pk')
|
||||
link_document_pages = Link(permissions=[permission_document_view], text=_('Pages'), view='documents:document_pages', args='resolved_object.pk')
|
||||
link_document_preview = Link(
|
||||
permissions=[permission_document_view], text=_('Preview'),
|
||||
view='documents:document_preview', args='object.id'
|
||||
)
|
||||
link_document_properties = Link(
|
||||
permissions=[permission_document_view], text=_('Properties'),
|
||||
view='documents:document_properties', args='object.id'
|
||||
)
|
||||
link_document_version_list = Link(
|
||||
permissions=[permission_document_view], text=_('Versions'),
|
||||
view='documents:document_version_list', args='object.pk'
|
||||
)
|
||||
link_document_pages = Link(
|
||||
permissions=[permission_document_view], text=_('Pages'),
|
||||
view='documents:document_pages', args='resolved_object.pk'
|
||||
)
|
||||
|
||||
# Actions
|
||||
link_document_clear_transformations = Link(permissions=[permission_transformation_delete], text=_('Clear transformations'), view='documents:document_clear_transformations', args='object.id')
|
||||
link_document_delete = Link(permissions=[permission_document_delete], tags='dangerous', text=_('Delete'), view='documents:document_delete', args='object.id')
|
||||
link_document_trash = Link(permissions=[permission_document_trash], tags='dangerous', text=_('Move to trash'), view='documents:document_trash', args='object.id')
|
||||
link_document_edit = Link(permissions=[permission_document_properties_edit], text=_('Edit properties'), view='documents:document_edit', args='object.id')
|
||||
link_document_document_type_edit = Link(permissions=[permission_document_properties_edit], text=_('Change type'), view='documents:document_document_type_edit', args='object.id')
|
||||
link_document_download = Link(permissions=[permission_document_download], text=_('Download'), view='documents:document_download', args='object.id')
|
||||
link_document_print = Link(permissions=[permission_document_print], text=_('Print'), view='documents:document_print', args='object.id')
|
||||
link_document_update_page_count = Link(permissions=[permission_document_tools], text=_('Reset page count'), view='documents:document_update_page_count', args='object.pk')
|
||||
link_document_restore = Link(permissions=[permission_document_restore], text=_('Restore'), view='documents:document_restore', args='object.pk')
|
||||
link_document_multiple_clear_transformations = Link(permissions=[permission_transformation_delete], text=_('Clear transformations'), view='documents:document_multiple_clear_transformations')
|
||||
link_document_multiple_trash = Link(permissions=[permission_document_trash], tags='dangerous', text=_('Move to trash'), view='documents:document_multiple_trash')
|
||||
link_document_multiple_delete = Link(permissions=[permission_document_delete], tags='dangerous', text=_('Delete'), view='documents:document_multiple_delete')
|
||||
link_document_multiple_document_type_edit = Link(permissions=[permission_document_properties_edit], text=_('Change type'), view='documents:document_multiple_document_type_edit')
|
||||
link_document_multiple_download = Link(permissions=[permission_document_download], text=_('Download'), view='documents:document_multiple_download')
|
||||
link_document_multiple_update_page_count = Link(permissions=[permission_document_tools], text=_('Reset page count'), view='documents:document_multiple_update_page_count')
|
||||
link_document_multiple_restore = Link(permissions=[permission_document_restore], text=_('Restore'), view='documents:document_multiple_restore')
|
||||
link_document_version_download = Link(args='object.pk', permissions=[permission_document_download], text=_('Download'), view='documents:document_version_download')
|
||||
link_document_clear_transformations = Link(
|
||||
permissions=[permission_transformation_delete],
|
||||
text=_('Clear transformations'),
|
||||
view='documents:document_clear_transformations', args='object.id'
|
||||
)
|
||||
link_document_delete = Link(
|
||||
permissions=[permission_document_delete], tags='dangerous',
|
||||
text=_('Delete'), view='documents:document_delete', args='object.id'
|
||||
)
|
||||
link_document_trash = Link(
|
||||
permissions=[permission_document_trash], tags='dangerous',
|
||||
text=_('Move to trash'), view='documents:document_trash', args='object.id'
|
||||
)
|
||||
link_document_edit = Link(
|
||||
permissions=[permission_document_properties_edit],
|
||||
text=_('Edit properties'), view='documents:document_edit',
|
||||
args='object.id'
|
||||
)
|
||||
link_document_document_type_edit = Link(
|
||||
permissions=[permission_document_properties_edit], text=_('Change type'),
|
||||
view='documents:document_document_type_edit', args='object.id'
|
||||
)
|
||||
link_document_download = Link(
|
||||
permissions=[permission_document_download], text=_('Download'),
|
||||
view='documents:document_download', args='object.id'
|
||||
)
|
||||
link_document_print = Link(
|
||||
permissions=[permission_document_print], text=_('Print'),
|
||||
view='documents:document_print', args='object.id'
|
||||
)
|
||||
link_document_update_page_count = Link(
|
||||
permissions=[permission_document_tools], text=_('Reset page count'),
|
||||
view='documents:document_update_page_count', args='object.pk'
|
||||
)
|
||||
link_document_restore = Link(
|
||||
permissions=[permission_document_restore], text=_('Restore'),
|
||||
view='documents:document_restore', args='object.pk'
|
||||
)
|
||||
link_document_multiple_clear_transformations = Link(
|
||||
permissions=[permission_transformation_delete],
|
||||
text=_('Clear transformations'),
|
||||
view='documents:document_multiple_clear_transformations'
|
||||
)
|
||||
link_document_multiple_trash = Link(
|
||||
permissions=[permission_document_trash], tags='dangerous',
|
||||
text=_('Move to trash'), view='documents:document_multiple_trash'
|
||||
)
|
||||
link_document_multiple_delete = Link(
|
||||
permissions=[permission_document_delete], tags='dangerous',
|
||||
text=_('Delete'), view='documents:document_multiple_delete'
|
||||
)
|
||||
link_document_multiple_document_type_edit = Link(
|
||||
permissions=[permission_document_properties_edit], text=_('Change type'),
|
||||
view='documents:document_multiple_document_type_edit'
|
||||
)
|
||||
link_document_multiple_download = Link(
|
||||
permissions=[permission_document_download], text=_('Download'),
|
||||
view='documents:document_multiple_download'
|
||||
)
|
||||
link_document_multiple_update_page_count = Link(
|
||||
permissions=[permission_document_tools], text=_('Reset page count'),
|
||||
view='documents:document_multiple_update_page_count'
|
||||
)
|
||||
link_document_multiple_restore = Link(
|
||||
permissions=[permission_document_restore], text=_('Restore'),
|
||||
view='documents:document_multiple_restore'
|
||||
)
|
||||
link_document_version_download = Link(
|
||||
args='object.pk', permissions=[permission_document_download],
|
||||
text=_('Download'), view='documents:document_version_download'
|
||||
)
|
||||
|
||||
# Views
|
||||
link_document_list = Link(icon='fa fa-file', text=_('All documents'), view='documents:document_list')
|
||||
link_document_list_recent = Link(icon='fa fa-clock-o', text=_('Recent documents'), view='documents:document_list_recent')
|
||||
link_document_list_deleted = Link(icon='fa fa-trash', text=_('Trash'), view='documents:document_list_deleted')
|
||||
link_document_list = Link(
|
||||
icon='fa fa-file', text=_('All documents'), view='documents:document_list'
|
||||
)
|
||||
link_document_list_recent = Link(
|
||||
icon='fa fa-clock-o', text=_('Recent documents'),
|
||||
view='documents:document_list_recent'
|
||||
)
|
||||
link_document_list_deleted = Link(
|
||||
icon='fa fa-trash', text=_('Trash'),
|
||||
view='documents:document_list_deleted'
|
||||
)
|
||||
|
||||
# Tools
|
||||
link_clear_image_cache = Link(
|
||||
icon='fa fa-file-image-o',
|
||||
description=_('Clear the graphics representations used to speed up the documents\' display and interactive transformations results.'),
|
||||
description=_(
|
||||
'Clear the graphics representations used to speed up the documents\' '
|
||||
'display and interactive transformations results.'
|
||||
),
|
||||
permissions=[permission_document_tools], text=_('Clear document cache'),
|
||||
view='documents:document_clear_image_cache'
|
||||
)
|
||||
link_trash_can_empty = Link(permissions=[permission_empty_trash], text=_('Empty trash'), view='documents:trash_can_empty')
|
||||
link_trash_can_empty = Link(
|
||||
permissions=[permission_empty_trash], text=_('Empty trash'),
|
||||
view='documents:trash_can_empty'
|
||||
)
|
||||
|
||||
# Document pages
|
||||
link_document_page_navigation_first = Link(conditional_disable=is_first_page, icon='fa fa-step-backward', keep_query=True, permissions=[permission_document_view], text=_('First page'), view='documents:document_page_navigation_first', args='resolved_object.pk')
|
||||
link_document_page_navigation_last = Link(conditional_disable=is_last_page, icon='fa fa-step-forward', keep_query=True, text=_('Last page'), permissions=[permission_document_view], view='documents:document_page_navigation_last', args='resolved_object.pk')
|
||||
link_document_page_navigation_previous = Link(conditional_disable=is_first_page, icon='fa fa-arrow-left', keep_query=True, permissions=[permission_document_view], text=_('Previous page'), view='documents:document_page_navigation_previous', args='resolved_object.pk')
|
||||
link_document_page_navigation_next = Link(conditional_disable=is_last_page, icon='fa fa-arrow-right', keep_query=True, text=_('Next page'), permissions=[permission_document_view], view='documents:document_page_navigation_next', args='resolved_object.pk')
|
||||
link_document_page_return = Link(icon='fa fa-file', permissions=[permission_document_view], text=_('Document'), view='documents:document_preview', args='resolved_object.document.pk')
|
||||
link_document_page_rotate_left = Link(icon='fa fa-rotate-left', permissions=[permission_document_view], text=_('Rotate left'), view='documents:document_page_rotate_left', args='resolved_object.pk')
|
||||
link_document_page_rotate_right = Link(icon='fa fa-rotate-right', permissions=[permission_document_view], text=_('Rotate right'), view='documents:document_page_rotate_right', args='resolved_object.pk')
|
||||
link_document_page_view = Link(permissions=[permission_document_view], text=_('Page image'), view='documents:document_page_view', args='resolved_object.pk')
|
||||
link_document_page_view_reset = Link(permissions=[permission_document_view], text=_('Reset view'), view='documents:document_page_view_reset', args='resolved_object.pk')
|
||||
link_document_page_zoom_in = Link(conditional_disable=is_max_zoom, icon='fa fa-search-plus', permissions=[permission_document_view], text=_('Zoom in'), view='documents:document_page_zoom_in', args='resolved_object.pk')
|
||||
link_document_page_zoom_out = Link(conditional_disable=is_min_zoom, icon='fa fa-search-minus', permissions=[permission_document_view], text=_('Zoom out'), view='documents:document_page_zoom_out', args='resolved_object.pk')
|
||||
link_document_page_navigation_first = Link(
|
||||
conditional_disable=is_first_page, icon='fa fa-step-backward',
|
||||
keep_query=True, permissions=[permission_document_view],
|
||||
text=_('First page'), view='documents:document_page_navigation_first',
|
||||
args='resolved_object.pk'
|
||||
)
|
||||
link_document_page_navigation_last = Link(
|
||||
conditional_disable=is_last_page, icon='fa fa-step-forward',
|
||||
keep_query=True, text=_('Last page'),
|
||||
permissions=[permission_document_view],
|
||||
view='documents:document_page_navigation_last', args='resolved_object.pk'
|
||||
)
|
||||
link_document_page_navigation_previous = Link(
|
||||
conditional_disable=is_first_page, icon='fa fa-arrow-left',
|
||||
keep_query=True, permissions=[permission_document_view],
|
||||
text=_('Previous page'),
|
||||
view='documents:document_page_navigation_previous',
|
||||
args='resolved_object.pk'
|
||||
)
|
||||
link_document_page_navigation_next = Link(
|
||||
conditional_disable=is_last_page, icon='fa fa-arrow-right',
|
||||
keep_query=True, text=_('Next page'),
|
||||
permissions=[permission_document_view],
|
||||
view='documents:document_page_navigation_next', args='resolved_object.pk'
|
||||
)
|
||||
link_document_page_return = Link(
|
||||
icon='fa fa-file', permissions=[permission_document_view],
|
||||
text=_('Document'), view='documents:document_preview',
|
||||
args='resolved_object.document.pk'
|
||||
)
|
||||
link_document_page_rotate_left = Link(
|
||||
icon='fa fa-rotate-left', permissions=[permission_document_view],
|
||||
text=_('Rotate left'), view='documents:document_page_rotate_left',
|
||||
args='resolved_object.pk'
|
||||
)
|
||||
link_document_page_rotate_right = Link(
|
||||
icon='fa fa-rotate-right', permissions=[permission_document_view],
|
||||
text=_('Rotate right'), view='documents:document_page_rotate_right',
|
||||
args='resolved_object.pk'
|
||||
)
|
||||
link_document_page_view = Link(
|
||||
permissions=[permission_document_view], text=_('Page image'),
|
||||
view='documents:document_page_view', args='resolved_object.pk'
|
||||
)
|
||||
link_document_page_view_reset = Link(
|
||||
permissions=[permission_document_view], text=_('Reset view'),
|
||||
view='documents:document_page_view_reset', args='resolved_object.pk'
|
||||
)
|
||||
link_document_page_zoom_in = Link(
|
||||
conditional_disable=is_max_zoom, icon='fa fa-search-plus',
|
||||
permissions=[permission_document_view], text=_('Zoom in'),
|
||||
view='documents:document_page_zoom_in', args='resolved_object.pk'
|
||||
)
|
||||
link_document_page_zoom_out = Link(
|
||||
conditional_disable=is_min_zoom, icon='fa fa-search-minus',
|
||||
permissions=[permission_document_view], text=_('Zoom out'),
|
||||
view='documents:document_page_zoom_out', args='resolved_object.pk'
|
||||
)
|
||||
|
||||
# Document versions
|
||||
link_document_version_revert = Link(condition=is_not_current_version, permissions=[permission_document_version_revert], tags='dangerous', text=_('Revert'), view='documents:document_version_revert', args='object.pk')
|
||||
link_document_version_revert = Link(
|
||||
condition=is_not_current_version,
|
||||
permissions=[permission_document_version_revert], tags='dangerous',
|
||||
text=_('Revert'), view='documents:document_version_revert',
|
||||
args='object.pk'
|
||||
)
|
||||
|
||||
# Document type related links
|
||||
link_document_type_create = Link(permissions=[permission_document_type_create], text=_('Create document type'), view='documents:document_type_create')
|
||||
link_document_type_delete = Link(permissions=[permission_document_type_delete], tags='dangerous', text=_('Delete'), view='documents:document_type_delete', args='resolved_object.id')
|
||||
link_document_type_edit = Link(permissions=[permission_document_type_edit], text=_('Edit'), view='documents:document_type_edit', args='resolved_object.id')
|
||||
link_document_type_filename_create = Link(permissions=[permission_document_type_edit], text=_('Add filename to document type'), view='documents:document_type_filename_create', args='document_type.id')
|
||||
link_document_type_filename_delete = Link(permissions=[permission_document_type_edit], tags='dangerous', text=_('Delete'), view='documents:document_type_filename_delete', args='resolved_object.id')
|
||||
link_document_type_filename_edit = Link(permissions=[permission_document_type_edit], text=_('Edit'), view='documents:document_type_filename_edit', args='resolved_object.id')
|
||||
link_document_type_filename_list = Link(permissions=[permission_document_type_view], text=_('Filenames'), view='documents:document_type_filename_list', args='resolved_object.id')
|
||||
link_document_type_list = Link(permissions=[permission_document_type_view], text=_('Document types'), view='documents:document_type_list')
|
||||
link_document_type_setup = Link(icon='fa fa-file', permissions=[permission_document_type_view], text=_('Document types'), view='documents:document_type_list')
|
||||
link_document_type_create = Link(
|
||||
permissions=[permission_document_type_create],
|
||||
text=_('Create document type'), view='documents:document_type_create'
|
||||
)
|
||||
link_document_type_delete = Link(
|
||||
permissions=[permission_document_type_delete], tags='dangerous',
|
||||
text=_('Delete'), view='documents:document_type_delete',
|
||||
args='resolved_object.id'
|
||||
)
|
||||
link_document_type_edit = Link(
|
||||
permissions=[permission_document_type_edit], text=_('Edit'),
|
||||
view='documents:document_type_edit', args='resolved_object.id'
|
||||
)
|
||||
link_document_type_filename_create = Link(
|
||||
permissions=[permission_document_type_edit],
|
||||
text=_('Add filename to document type'),
|
||||
view='documents:document_type_filename_create', args='document_type.id'
|
||||
)
|
||||
link_document_type_filename_delete = Link(
|
||||
permissions=[permission_document_type_edit], tags='dangerous',
|
||||
text=_('Delete'), view='documents:document_type_filename_delete',
|
||||
args='resolved_object.id'
|
||||
)
|
||||
link_document_type_filename_edit = Link(
|
||||
permissions=[permission_document_type_edit], text=_('Edit'),
|
||||
view='documents:document_type_filename_edit', args='resolved_object.id'
|
||||
)
|
||||
link_document_type_filename_list = Link(
|
||||
permissions=[permission_document_type_view], text=_('Filenames'),
|
||||
view='documents:document_type_filename_list', args='resolved_object.id'
|
||||
)
|
||||
link_document_type_list = Link(
|
||||
permissions=[permission_document_type_view], text=_('Document types'),
|
||||
view='documents:document_type_list'
|
||||
)
|
||||
link_document_type_setup = Link(
|
||||
icon='fa fa-file', permissions=[permission_document_type_view],
|
||||
text=_('Document types'), view='documents:document_type_list'
|
||||
)
|
||||
|
||||
@@ -13,7 +13,9 @@ logger = logging.getLogger(__name__)
|
||||
class RecentDocumentManager(models.Manager):
|
||||
def add_document_for_user(self, user, document):
|
||||
if user.is_authenticated():
|
||||
new_recent, created = self.model.objects.get_or_create(user=user, document=document)
|
||||
new_recent, created = self.model.objects.get_or_create(
|
||||
user=user, document=document
|
||||
)
|
||||
if not created:
|
||||
# document already in the recent list, just save to force
|
||||
# accessed date and time update
|
||||
@@ -25,7 +27,9 @@ class RecentDocumentManager(models.Manager):
|
||||
document_model = apps.get_model('documents', 'document')
|
||||
|
||||
if user.is_authenticated():
|
||||
return document_model.objects.filter(recentdocument__user=user).order_by('-recentdocument__datetime_accessed')
|
||||
return document_model.objects.filter(
|
||||
recentdocument__user=user
|
||||
).order_by('-recentdocument__datetime_accessed')
|
||||
else:
|
||||
return document_model.objects.none()
|
||||
|
||||
@@ -37,7 +41,9 @@ class DocumentTypeManager(models.Manager):
|
||||
|
||||
class DocumentManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return TrashCanQuerySet(self.model, using=self._db).filter(in_trash=False)
|
||||
return TrashCanQuerySet(
|
||||
self.model, using=self._db
|
||||
).filter(in_trash=False)
|
||||
|
||||
def invalidate_cache(self):
|
||||
for document in self.model.objects.all():
|
||||
@@ -50,7 +56,9 @@ class PassthroughManager(models.Manager):
|
||||
|
||||
class TrashCanManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return super(TrashCanManager, self).get_queryset().filter(in_trash=True)
|
||||
return super(
|
||||
TrashCanManager, self
|
||||
).get_queryset().filter(in_trash=True)
|
||||
|
||||
|
||||
class TrashCanQuerySet(models.QuerySet):
|
||||
|
||||
@@ -43,7 +43,8 @@ from .signals import (
|
||||
post_document_created, post_document_type_change, post_version_upload
|
||||
)
|
||||
|
||||
HASH_FUNCTION = lambda x: hashlib.sha256(x).hexdigest() # document image cache name hash function
|
||||
# document image cache name hash function
|
||||
HASH_FUNCTION = lambda x: hashlib.sha256(x).hexdigest()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -57,11 +58,29 @@ class DocumentType(models.Model):
|
||||
Define document types or classes to which a specific set of
|
||||
properties can be attached
|
||||
"""
|
||||
label = models.CharField(max_length=32, unique=True, verbose_name=_('Label'))
|
||||
trash_time_period = models.PositiveIntegerField(blank=True, help_text=_('Amount of time after which documents of this type will be moved to the trash.'), null=True, verbose_name=_('Trash time period'))
|
||||
trash_time_unit = models.CharField(blank=True, choices=TIME_DELTA_UNIT_CHOICES, null=True, max_length=8, verbose_name=_('Trash time unit'))
|
||||
delete_time_period = models.PositiveIntegerField(default=DEFAULT_DELETE_PERIOD, help_text=_('Amount of time after which documents of this type in the trash will be deleted.'), verbose_name=_('Delete time period'))
|
||||
delete_time_unit = models.CharField(choices=TIME_DELTA_UNIT_CHOICES, default=DEFAULT_DELETE_TIME_UNIT, max_length=8, verbose_name=_('Delete time unit'))
|
||||
label = models.CharField(
|
||||
max_length=32, unique=True, verbose_name=_('Label')
|
||||
)
|
||||
trash_time_period = models.PositiveIntegerField(
|
||||
blank=True, help_text=_(
|
||||
'Amount of time after which documents of this type will be '
|
||||
'moved to the trash.'
|
||||
), null=True, verbose_name=_('Trash time period')
|
||||
)
|
||||
trash_time_unit = models.CharField(
|
||||
blank=True, choices=TIME_DELTA_UNIT_CHOICES, null=True, max_length=8,
|
||||
verbose_name=_('Trash time unit')
|
||||
)
|
||||
delete_time_period = models.PositiveIntegerField(
|
||||
default=DEFAULT_DELETE_PERIOD, help_text=_(
|
||||
'Amount of time after which documents of this type in the trash '
|
||||
'will be deleted.'
|
||||
), verbose_name=_('Delete time period')
|
||||
)
|
||||
delete_time_unit = models.CharField(
|
||||
choices=TIME_DELTA_UNIT_CHOICES, default=DEFAULT_DELETE_TIME_UNIT,
|
||||
max_length=8, verbose_name=_('Delete time unit')
|
||||
)
|
||||
|
||||
objects = DocumentTypeManager()
|
||||
|
||||
@@ -77,13 +96,21 @@ class DocumentType(models.Model):
|
||||
def new_document(self, file_object, label=None, description=None, language=None, _user=None):
|
||||
try:
|
||||
with transaction.atomic():
|
||||
document = self.documents.create(description=description or '', label=label or unicode(file_object), language=language or setting_language.value)
|
||||
document = self.documents.create(
|
||||
description=description or '',
|
||||
label=label or unicode(file_object),
|
||||
language=language or setting_language.value
|
||||
)
|
||||
document.save(_user=_user)
|
||||
|
||||
document.new_version(file_object=file_object, _user=_user)
|
||||
return document
|
||||
except Exception as exception:
|
||||
logger.critical('Unexpected exception while trying to create new document "%s" from document type "%s"; %s', label or unicode(file_object), self, exception)
|
||||
logger.critical(
|
||||
'Unexpected exception while trying to create new document '
|
||||
'"%s" from document type "%s"; %s',
|
||||
label or unicode(file_object), self, exception
|
||||
)
|
||||
raise
|
||||
|
||||
class Meta:
|
||||
@@ -98,15 +125,38 @@ class Document(models.Model):
|
||||
Defines a single document with it's fields and properties
|
||||
"""
|
||||
|
||||
uuid = models.CharField(default=UUID_FUNCTION, editable=False, max_length=48)
|
||||
document_type = models.ForeignKey(DocumentType, related_name='documents', verbose_name=_('Document type'))
|
||||
label = models.CharField(blank=True, db_index=True, default='', max_length=255, help_text=_('The name of the document'), verbose_name=_('Label'))
|
||||
description = models.TextField(blank=True, default='', verbose_name=_('Description'))
|
||||
date_added = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name=_('Added'))
|
||||
language = models.CharField(blank=True, choices=setting_language_choices.value, default=setting_language.value, max_length=8, verbose_name=_('Language'))
|
||||
in_trash = models.BooleanField(default=False, editable=False, verbose_name=_('In trash?'))
|
||||
deleted_date_time = models.DateTimeField(blank=True, editable=True, null=True, verbose_name=_('Date and time trashed'))
|
||||
is_stub = models.BooleanField(default=True, editable=False, verbose_name=_('Is stub?'))
|
||||
uuid = models.CharField(
|
||||
default=UUID_FUNCTION, editable=False, max_length=48
|
||||
)
|
||||
document_type = models.ForeignKey(
|
||||
DocumentType, related_name='documents',
|
||||
verbose_name=_('Document type')
|
||||
)
|
||||
label = models.CharField(
|
||||
blank=True, db_index=True, default='', max_length=255,
|
||||
help_text=_('The name of the document'), verbose_name=_('Label')
|
||||
)
|
||||
description = models.TextField(
|
||||
blank=True, default='', verbose_name=_('Description')
|
||||
)
|
||||
date_added = models.DateTimeField(
|
||||
auto_now_add=True, db_index=True, verbose_name=_('Added')
|
||||
)
|
||||
language = models.CharField(
|
||||
blank=True, choices=setting_language_choices.value,
|
||||
default=setting_language.value, max_length=8,
|
||||
verbose_name=_('Language')
|
||||
)
|
||||
in_trash = models.BooleanField(
|
||||
default=False, editable=False, verbose_name=_('In trash?')
|
||||
)
|
||||
deleted_date_time = models.DateTimeField(
|
||||
blank=True, editable=True, null=True,
|
||||
verbose_name=_('Date and time trashed')
|
||||
)
|
||||
is_stub = models.BooleanField(
|
||||
default=True, editable=False, verbose_name=_('Is stub?')
|
||||
)
|
||||
|
||||
objects = DocumentManager()
|
||||
passthrough = PassthroughManager()
|
||||
@@ -123,7 +173,9 @@ class Document(models.Model):
|
||||
self.document_type = document_type
|
||||
self.save()
|
||||
if has_changed or force:
|
||||
post_document_type_change.send(sender=self.__class__, instance=self)
|
||||
post_document_type_change.send(
|
||||
sender=self.__class__, instance=self
|
||||
)
|
||||
|
||||
def invalidate_cache(self):
|
||||
for document_version in self.versions.all():
|
||||
@@ -174,7 +226,9 @@ class Document(models.Model):
|
||||
def new_version(self, file_object, comment=None, _user=None):
|
||||
logger.info('Creating new document version for document: %s', self)
|
||||
|
||||
document_version = DocumentVersion(document=self, comment=comment or '', file=File(file_object))
|
||||
document_version = DocumentVersion(
|
||||
document=self, comment=comment or '', file=File(file_object)
|
||||
)
|
||||
document_version.save(_user=_user)
|
||||
|
||||
logger.info('New document version queued for document: %s', self)
|
||||
@@ -238,7 +292,9 @@ class Document(models.Model):
|
||||
|
||||
# TODO: look to remove, only used by the OCR parser
|
||||
def document_save_to_temp_dir(self, filename, buffer_size=1024 * 1024):
|
||||
temporary_path = os.path.join(setting_temporary_directory.value, filename)
|
||||
temporary_path = os.path.join(
|
||||
setting_temporary_directory.value, filename
|
||||
)
|
||||
return self.save_to_file(temporary_path, buffer_size)
|
||||
|
||||
|
||||
@@ -265,15 +321,30 @@ class DocumentVersion(models.Model):
|
||||
def register_post_save_hook(cls, order, func):
|
||||
cls._post_save_hooks[order] = func
|
||||
|
||||
document = models.ForeignKey(Document, related_name='versions', verbose_name=_('Document'))
|
||||
timestamp = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name=_('Timestamp'))
|
||||
comment = models.TextField(blank=True, default='', verbose_name=_('Comment'))
|
||||
document = models.ForeignKey(
|
||||
Document, related_name='versions', verbose_name=_('Document')
|
||||
)
|
||||
timestamp = models.DateTimeField(
|
||||
auto_now_add=True, db_index=True, verbose_name=_('Timestamp')
|
||||
)
|
||||
comment = models.TextField(
|
||||
blank=True, default='', verbose_name=_('Comment')
|
||||
)
|
||||
|
||||
# File related fields
|
||||
file = models.FileField(storage=storage_backend, upload_to=UUID_FUNCTION, verbose_name=_('File'))
|
||||
mimetype = models.CharField(blank=True, editable=False, max_length=255, null=True)
|
||||
encoding = models.CharField(blank=True, editable=False, max_length=64, null=True)
|
||||
checksum = models.TextField(blank=True, editable=False, null=True, verbose_name=_('Checksum'))
|
||||
file = models.FileField(
|
||||
storage=storage_backend, upload_to=UUID_FUNCTION,
|
||||
verbose_name=_('File')
|
||||
)
|
||||
mimetype = models.CharField(
|
||||
blank=True, editable=False, max_length=255, null=True
|
||||
)
|
||||
encoding = models.CharField(
|
||||
blank=True, editable=False, max_length=64, null=True
|
||||
)
|
||||
checksum = models.TextField(
|
||||
blank=True, editable=False, null=True, verbose_name=_('Checksum')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Document version')
|
||||
@@ -308,7 +379,10 @@ class DocumentVersion(models.Model):
|
||||
self.save()
|
||||
self.update_page_count(save=False)
|
||||
|
||||
logger.info('New document version "%s" created for document: %s', self, self.document)
|
||||
logger.info(
|
||||
'New document version "%s" created for document: %s',
|
||||
self, self.document
|
||||
)
|
||||
|
||||
self.document.is_stub = False
|
||||
if not self.document.label:
|
||||
@@ -316,15 +390,22 @@ class DocumentVersion(models.Model):
|
||||
|
||||
self.document.save()
|
||||
except Exception as exception:
|
||||
logger.error('Error creating new document version for document "%s"; %s', self.document, exception)
|
||||
logger.error(
|
||||
'Error creating new document version for document "%s"; %s',
|
||||
self.document, exception
|
||||
)
|
||||
raise
|
||||
else:
|
||||
if new_document_version:
|
||||
event_document_new_version.commit(actor=user, target=self.document)
|
||||
event_document_new_version.commit(
|
||||
actor=user, target=self.document
|
||||
)
|
||||
post_version_upload.send(sender=self.__class__, instance=self)
|
||||
|
||||
if tuple(self.document.versions.all()) == (self,):
|
||||
post_document_created.send(sender=self.document.__class__, instance=self.document)
|
||||
post_document_created.send(
|
||||
sender=self.document.__class__, instance=self.document
|
||||
)
|
||||
|
||||
def invalidate_cache(self):
|
||||
cache_storage_backend.delete(self.cache_filename)
|
||||
@@ -333,8 +414,8 @@ class DocumentVersion(models.Model):
|
||||
|
||||
def update_checksum(self, save=True):
|
||||
"""
|
||||
Open a document version's file and update the checksum field using the
|
||||
user provided checksum function
|
||||
Open a document version's file and update the checksum field using
|
||||
the user provided checksum function
|
||||
"""
|
||||
if self.exists():
|
||||
source = self.open()
|
||||
@@ -346,7 +427,9 @@ class DocumentVersion(models.Model):
|
||||
def update_page_count(self, save=True):
|
||||
try:
|
||||
with self.open() as file_object:
|
||||
converter = converter_class(file_object=file_object, mime_type=self.mimetype)
|
||||
converter = converter_class(
|
||||
file_object=file_object, mime_type=self.mimetype
|
||||
)
|
||||
detected_pages = converter.get_page_count()
|
||||
except UnknownFileFormat:
|
||||
# If converter backend doesn't understand the format,
|
||||
@@ -372,7 +455,10 @@ class DocumentVersion(models.Model):
|
||||
"""
|
||||
Delete the subsequent versions after this one
|
||||
"""
|
||||
logger.info('Reverting to document document: %s to version: %s', self.document, self)
|
||||
logger.info(
|
||||
'Reverting to document document: %s to version: %s',
|
||||
self.document, self
|
||||
)
|
||||
|
||||
event_document_version_revert.commit(actor=user, target=self.document)
|
||||
|
||||
@@ -381,13 +467,15 @@ class DocumentVersion(models.Model):
|
||||
|
||||
def update_mimetype(self, save=True):
|
||||
"""
|
||||
Read a document verions's file and determine the mimetype by calling the
|
||||
get_mimetype wrapper
|
||||
Read a document verions's file and determine the mimetype by calling
|
||||
the get_mimetype wrapper
|
||||
"""
|
||||
if self.exists():
|
||||
try:
|
||||
with self.open() as file_object:
|
||||
self.mimetype, self.encoding = get_mimetype(file_object=file_object)
|
||||
self.mimetype, self.encoding = get_mimetype(
|
||||
file_object=file_object
|
||||
)
|
||||
except:
|
||||
self.mimetype = ''
|
||||
self.encoding = ''
|
||||
@@ -486,7 +574,10 @@ class DocumentVersion(models.Model):
|
||||
return self.open()
|
||||
except Exception as exception:
|
||||
# Cleanup in case of error
|
||||
logger.error('Error creating intermediate file "%s"; %s.', cache_filename, exception)
|
||||
logger.error(
|
||||
'Error creating intermediate file "%s"; %s.',
|
||||
cache_filename, exception
|
||||
)
|
||||
cache_storage_backend.delete(cache_filename)
|
||||
raise
|
||||
|
||||
@@ -497,8 +588,12 @@ class DocumentTypeFilename(models.Model):
|
||||
List of filenames available to a specific document type for the
|
||||
quick rename functionality
|
||||
"""
|
||||
document_type = models.ForeignKey(DocumentType, related_name='filenames', verbose_name=_('Document type'))
|
||||
filename = models.CharField(db_index=True, max_length=128, verbose_name=_('Filename'))
|
||||
document_type = models.ForeignKey(
|
||||
DocumentType, related_name='filenames', verbose_name=_('Document type')
|
||||
)
|
||||
filename = models.CharField(
|
||||
db_index=True, max_length=128, verbose_name=_('Filename')
|
||||
)
|
||||
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
|
||||
|
||||
def __str__(self):
|
||||
@@ -516,11 +611,18 @@ class DocumentPage(models.Model):
|
||||
"""
|
||||
Model that describes a document version page
|
||||
"""
|
||||
document_version = models.ForeignKey(DocumentVersion, related_name='pages', verbose_name=_('Document version'))
|
||||
page_number = models.PositiveIntegerField(db_index=True, default=1, editable=False, verbose_name=_('Page number'))
|
||||
document_version = models.ForeignKey(
|
||||
DocumentVersion, related_name='pages',
|
||||
verbose_name=_('Document version')
|
||||
)
|
||||
page_number = models.PositiveIntegerField(
|
||||
db_index=True, default=1, editable=False, verbose_name=_('Page number')
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return _('Page %(page_num)d out of %(total_pages)d of %(document)s') % {
|
||||
return _(
|
||||
'Page %(page_num)d out of %(total_pages)d of %(document)s'
|
||||
) % {
|
||||
'document': unicode(self.document),
|
||||
'page_num': self.page_number,
|
||||
'total_pages': self.document_version.pages.count()
|
||||
@@ -540,7 +642,9 @@ class DocumentPage(models.Model):
|
||||
|
||||
@property
|
||||
def siblings(self):
|
||||
return DocumentPage.objects.filter(document_version=self.document_version)
|
||||
return DocumentPage.objects.filter(
|
||||
document_version=self.document_version
|
||||
)
|
||||
|
||||
# Compatibility methods
|
||||
@property
|
||||
@@ -582,14 +686,18 @@ class DocumentPage(models.Model):
|
||||
|
||||
if cache_storage_backend.exists(cache_filename):
|
||||
logger.debug('Page cache file "%s" found', cache_filename)
|
||||
converter = converter_class(file_object=cache_storage_backend.open(cache_filename))
|
||||
converter = converter_class(
|
||||
file_object=cache_storage_backend.open(cache_filename)
|
||||
)
|
||||
|
||||
converter.seek(0)
|
||||
else:
|
||||
logger.debug('Page cache file "%s" not found', cache_filename)
|
||||
|
||||
try:
|
||||
converter = converter_class(file_object=self.document_version.get_intermidiate_file())
|
||||
converter = converter_class(
|
||||
file_object=self.document_version.get_intermidiate_file()
|
||||
)
|
||||
converter.seek(page_number=self.page_number - 1)
|
||||
|
||||
page_image = converter.get_page()
|
||||
@@ -598,7 +706,10 @@ class DocumentPage(models.Model):
|
||||
file_object.write(page_image.getvalue())
|
||||
except Exception as exception:
|
||||
# Cleanup in case of error
|
||||
logger.error('Error creating page cache file "%s"; %s', cache_filename, exception)
|
||||
logger.error(
|
||||
'Error creating page cache file "%s"; %s',
|
||||
cache_filename, exception
|
||||
)
|
||||
cache_storage_backend.delete(cache_filename)
|
||||
raise
|
||||
|
||||
@@ -611,13 +722,19 @@ class DocumentPage(models.Model):
|
||||
converter.transform(transformation=transformation)
|
||||
|
||||
if rotation:
|
||||
converter.transform(transformation=TransformationRotate(degrees=rotation))
|
||||
converter.transform(transformation=TransformationRotate(
|
||||
degrees=rotation)
|
||||
)
|
||||
|
||||
if size:
|
||||
converter.transform(transformation=TransformationResize(**dict(zip(('width', 'height'), (size.split('x'))))))
|
||||
converter.transform(transformation=TransformationResize(
|
||||
**dict(zip(('width', 'height'), (size.split('x')))))
|
||||
)
|
||||
|
||||
if zoom_level:
|
||||
converter.transform(transformation=TransformationZoom(percent=zoom_level))
|
||||
converter.transform(
|
||||
transformation=TransformationZoom(percent=zoom_level)
|
||||
)
|
||||
|
||||
page_image = converter.get_page()
|
||||
|
||||
@@ -635,8 +752,12 @@ class RecentDocument(models.Model):
|
||||
a given user
|
||||
"""
|
||||
user = models.ForeignKey(User, editable=False, verbose_name=_('User'))
|
||||
document = models.ForeignKey(Document, editable=False, verbose_name=_('Document'))
|
||||
datetime_accessed = models.DateTimeField(auto_now=True, db_index=True, verbose_name=_('Accessed'))
|
||||
document = models.ForeignKey(
|
||||
Document, editable=False, verbose_name=_('Document')
|
||||
)
|
||||
datetime_accessed = models.DateTimeField(
|
||||
auto_now=True, db_index=True, verbose_name=_('Accessed')
|
||||
)
|
||||
|
||||
objects = RecentDocumentManager()
|
||||
|
||||
|
||||
@@ -6,24 +6,61 @@ from permissions import PermissionNamespace
|
||||
|
||||
namespace = PermissionNamespace('documents', _('Documents'))
|
||||
|
||||
permission_document_create = namespace.add_permission(name='document_create', label=_('Create documents'))
|
||||
permission_document_delete = namespace.add_permission(name='document_delete', label=_('Delete documents'))
|
||||
permission_document_trash = namespace.add_permission(name='document_trash', label=_('Trash documents'))
|
||||
permission_document_download = namespace.add_permission(name='document_download', label=_('Download documents'))
|
||||
permission_document_edit = namespace.add_permission(name='document_edit', label=_('Edit documents'))
|
||||
permission_document_new_version = namespace.add_permission(name='document_new_version', label=_('Create new document versions'))
|
||||
permission_document_properties_edit = namespace.add_permission(name='document_properties_edit', label=_('Edit document properties'))
|
||||
permission_document_print = namespace.add_permission(name='document_print', label=_('Can print documents'))
|
||||
permission_document_restore = namespace.add_permission(name='document_restore', label=_('Restore deleted document'))
|
||||
permission_document_tools = namespace.add_permission(name='document_tools', label=_('Execute document modifying tools'))
|
||||
permission_document_version_revert = namespace.add_permission(name='document_version_revert', label=_('Revert documents to a previous version'))
|
||||
permission_document_view = namespace.add_permission(name='document_view', label=_('View documents'))
|
||||
permission_document_create = namespace.add_permission(
|
||||
name='document_create', label=_('Create documents')
|
||||
)
|
||||
permission_document_delete = namespace.add_permission(
|
||||
name='document_delete', label=_('Delete documents')
|
||||
)
|
||||
permission_document_trash = namespace.add_permission(
|
||||
name='document_trash', label=_('Trash documents')
|
||||
)
|
||||
permission_document_download = namespace.add_permission(
|
||||
name='document_download', label=_('Download documents')
|
||||
)
|
||||
permission_document_edit = namespace.add_permission(
|
||||
name='document_edit', label=_('Edit documents')
|
||||
)
|
||||
permission_document_new_version = namespace.add_permission(
|
||||
name='document_new_version', label=_('Create new document versions')
|
||||
)
|
||||
permission_document_properties_edit = namespace.add_permission(
|
||||
name='document_properties_edit', label=_('Edit document properties')
|
||||
)
|
||||
permission_document_print = namespace.add_permission(
|
||||
name='document_print', label=_('Can print documents')
|
||||
)
|
||||
permission_document_restore = namespace.add_permission(
|
||||
name='document_restore', label=_('Restore deleted document')
|
||||
)
|
||||
permission_document_tools = namespace.add_permission(
|
||||
name='document_tools', label=_('Execute document modifying tools')
|
||||
)
|
||||
permission_document_version_revert = namespace.add_permission(
|
||||
name='document_version_revert',
|
||||
label=_('Revert documents to a previous version')
|
||||
)
|
||||
permission_document_view = namespace.add_permission(
|
||||
name='document_view', label=_('View documents')
|
||||
)
|
||||
|
||||
permission_empty_trash = namespace.add_permission(name='document_empty_trash', label=_('Empty trash'))
|
||||
permission_empty_trash = namespace.add_permission(
|
||||
name='document_empty_trash', label=_('Empty trash')
|
||||
)
|
||||
|
||||
setup_namespace = PermissionNamespace('documents_setup', label=_('Documents setup'))
|
||||
setup_namespace = PermissionNamespace(
|
||||
'documents_setup', label=_('Documents setup')
|
||||
)
|
||||
|
||||
permission_document_type_create = setup_namespace.add_permission(name='document_type_create', label=_('Create document types'))
|
||||
permission_document_type_delete = setup_namespace.add_permission(name='document_type_delete', label=_('Delete document types'))
|
||||
permission_document_type_edit = setup_namespace.add_permission(name='document_type_edit', label=_('Edit document types'))
|
||||
permission_document_type_view = setup_namespace.add_permission(name='document_type_view', label=_('View document types'))
|
||||
permission_document_type_create = setup_namespace.add_permission(
|
||||
name='document_type_create', label=_('Create document types')
|
||||
)
|
||||
permission_document_type_delete = setup_namespace.add_permission(
|
||||
name='document_type_delete', label=_('Delete document types')
|
||||
)
|
||||
permission_document_type_edit = setup_namespace.add_permission(
|
||||
name='document_type_edit', label=_('Edit document types')
|
||||
)
|
||||
permission_document_type_view = setup_namespace.add_permission(
|
||||
name='document_type_view', label=_('View document types')
|
||||
)
|
||||
|
||||
@@ -6,9 +6,16 @@ from dynamic_search.classes import SearchModel
|
||||
|
||||
from .permissions import permission_document_view
|
||||
|
||||
document_search = SearchModel('documents', 'Document', permission=permission_document_view, serializer_string='documents.serializers.DocumentSerializer')
|
||||
document_search = SearchModel(
|
||||
'documents', 'Document', permission=permission_document_view,
|
||||
serializer_string='documents.serializers.DocumentSerializer'
|
||||
)
|
||||
|
||||
document_search.add_model_field(field='document_type__label', label=_('Document type'))
|
||||
document_search.add_model_field(field='versions__mimetype', label=_('MIME type'))
|
||||
document_search.add_model_field(
|
||||
field='document_type__label', label=_('Document type')
|
||||
)
|
||||
document_search.add_model_field(
|
||||
field='versions__mimetype', label=_('MIME type')
|
||||
)
|
||||
document_search.add_model_field(field='label', label=_('Label'))
|
||||
document_search.add_model_field(field='description', label=_('Description'))
|
||||
|
||||
@@ -40,11 +40,16 @@ class DocumentSerializer(serializers.ModelSerializer):
|
||||
versions = DocumentVersionSerializer(many=True, read_only=True)
|
||||
# TODO: Deprecate, move this as an entry point of DocumentVersion's pages
|
||||
image = serializers.HyperlinkedIdentityField(view_name='document-image')
|
||||
new_version = serializers.HyperlinkedIdentityField(view_name='document-new-version')
|
||||
new_version = serializers.HyperlinkedIdentityField(
|
||||
view_name='document-new-version'
|
||||
)
|
||||
document_type = DocumentTypeSerializer()
|
||||
|
||||
class Meta:
|
||||
fields = ('id', 'label', 'image', 'new_version', 'uuid', 'document_type', 'description', 'date_added', 'versions')
|
||||
fields = (
|
||||
'id', 'label', 'image', 'new_version', 'uuid', 'document_type',
|
||||
'description', 'date_added', 'versions'
|
||||
)
|
||||
model = Document
|
||||
|
||||
|
||||
@@ -53,7 +58,10 @@ class NewDocumentSerializer(serializers.Serializer):
|
||||
document_type = serializers.IntegerField()
|
||||
file = serializers.FileField()
|
||||
label = serializers.CharField(required=False)
|
||||
language = serializers.ChoiceField(blank_display_value=None, choices=setting_language_choices.value, default=setting_language.value, required=False)
|
||||
language = serializers.ChoiceField(
|
||||
blank_display_value=None, choices=setting_language_choices.value,
|
||||
default=setting_language.value, required=False
|
||||
)
|
||||
|
||||
|
||||
class RecentDocumentSerializer(serializers.ModelSerializer):
|
||||
|
||||
@@ -7,21 +7,75 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from smart_settings import Namespace
|
||||
|
||||
# TODO: Findout method to make languages names' translatable.
|
||||
# YAML fails to serialize ugettext_lazy and ugettext is not allowed at this level
|
||||
LANGUAGE_CHOICES = [(i.terminology, i.name) for i in list(pycountry.languages)]
|
||||
# YAML fails to serialize ugettext_lazy and ugettext is not allowed at this
|
||||
# level
|
||||
LANGUAGE_CHOICES = [
|
||||
(i.terminology, i.name) for i in list(pycountry.languages)
|
||||
]
|
||||
|
||||
namespace = Namespace(name='documents', label=_('Documents'))
|
||||
setting_storage_backend = namespace.add_setting(global_name='DOCUMENTS_STORAGE_BACKEND', default='storage.backends.filebasedstorage.FileBasedStorage')
|
||||
setting_preview_size = namespace.add_setting(global_name='DOCUMENTS_PREVIEW_SIZE', default='640x480')
|
||||
setting_print_size = namespace.add_setting(global_name='DOCUMENTS_PRINT_SIZE', default='3600')
|
||||
setting_multipage_preview_size = namespace.add_setting(global_name='DOCUMENTS_MULTIPAGE_PREVIEW_SIZE', default='160x120')
|
||||
setting_thumbnail_size = namespace.add_setting(global_name='DOCUMENTS_THUMBNAIL_SIZE', default='50x50')
|
||||
setting_display_size = namespace.add_setting(global_name='DOCUMENTS_DISPLAY_SIZE', default='3600')
|
||||
setting_recent_count = namespace.add_setting(global_name='DOCUMENTS_RECENT_COUNT', default=40, help_text=_('Maximum number of recent (created, edited, viewed) documents to remember per user.'))
|
||||
setting_zoom_percent_step = namespace.add_setting(global_name='DOCUMENTS_ZOOM_PERCENT_STEP', default=25, help_text=_('Amount in percent zoom in or out a document page per user interaction.'))
|
||||
setting_zoom_max_level = namespace.add_setting(global_name='DOCUMENTS_ZOOM_MAX_LEVEL', default=300, help_text=_('Maximum amount in percent (%) to allow user to zoom in a document page interactively.'))
|
||||
setting_zoom_min_level = namespace.add_setting(global_name='DOCUMENTS_ZOOM_MIN_LEVEL', default=25, help_text=_('Minimum amount in percent (%) to allow user to zoom out a document page interactively.'))
|
||||
setting_rotation_step = namespace.add_setting(global_name='DOCUMENTS_ROTATION_STEP', default=90, help_text=_('Amount in degrees to rotate a document page per user interaction.'))
|
||||
setting_cache_storage_backend = namespace.add_setting(global_name='DOCUMENTS_CACHE_STORAGE_BACKEND', default='documents.storage.LocalCacheFileStorage')
|
||||
setting_language = namespace.add_setting(global_name='DOCUMENTS_LANGUAGE', default='eng', help_text=_('Default documents language (in ISO639-2 format).'))
|
||||
setting_language_choices = namespace.add_setting(global_name='DOCUMENTS_LANGUAGE_CHOICES', default=LANGUAGE_CHOICES, help_text=_('List of supported document languages.'))
|
||||
setting_storage_backend = namespace.add_setting(
|
||||
global_name='DOCUMENTS_STORAGE_BACKEND',
|
||||
default='storage.backends.filebasedstorage.FileBasedStorage'
|
||||
)
|
||||
setting_preview_size = namespace.add_setting(
|
||||
global_name='DOCUMENTS_PREVIEW_SIZE', default='640x480'
|
||||
)
|
||||
setting_print_size = namespace.add_setting(
|
||||
global_name='DOCUMENTS_PRINT_SIZE', default='3600'
|
||||
)
|
||||
setting_multipage_preview_size = namespace.add_setting(
|
||||
global_name='DOCUMENTS_MULTIPAGE_PREVIEW_SIZE', default='160x120'
|
||||
)
|
||||
setting_thumbnail_size = namespace.add_setting(
|
||||
global_name='DOCUMENTS_THUMBNAIL_SIZE', default='50x50'
|
||||
)
|
||||
setting_display_size = namespace.add_setting(
|
||||
global_name='DOCUMENTS_DISPLAY_SIZE', default='3600'
|
||||
)
|
||||
setting_recent_count = namespace.add_setting(
|
||||
global_name='DOCUMENTS_RECENT_COUNT', default=40,
|
||||
help_text=_(
|
||||
'Maximum number of recent (created, edited, viewed) documents to '
|
||||
'remember per user.'
|
||||
)
|
||||
)
|
||||
setting_zoom_percent_step = namespace.add_setting(
|
||||
global_name='DOCUMENTS_ZOOM_PERCENT_STEP', default=25,
|
||||
help_text=_(
|
||||
'Amount in percent zoom in or out a document page per user '
|
||||
'interaction.'
|
||||
)
|
||||
)
|
||||
setting_zoom_max_level = namespace.add_setting(
|
||||
global_name='DOCUMENTS_ZOOM_MAX_LEVEL', default=300,
|
||||
help_text=_(
|
||||
'Maximum amount in percent (%) to allow user to zoom in a document '
|
||||
'page interactively.'
|
||||
)
|
||||
)
|
||||
setting_zoom_min_level = namespace.add_setting(
|
||||
global_name='DOCUMENTS_ZOOM_MIN_LEVEL', default=25,
|
||||
help_text=_(
|
||||
'Minimum amount in percent (%) to allow user to zoom out a document '
|
||||
'page interactively.'
|
||||
)
|
||||
)
|
||||
setting_rotation_step = namespace.add_setting(
|
||||
global_name='DOCUMENTS_ROTATION_STEP', default=90,
|
||||
help_text=_(
|
||||
'Amount in degrees to rotate a document page per user interaction.'
|
||||
)
|
||||
)
|
||||
setting_cache_storage_backend = namespace.add_setting(
|
||||
global_name='DOCUMENTS_CACHE_STORAGE_BACKEND',
|
||||
default='documents.storage.LocalCacheFileStorage'
|
||||
)
|
||||
setting_language = namespace.add_setting(
|
||||
global_name='DOCUMENTS_LANGUAGE', default='eng',
|
||||
help_text=_('Default documents language (in ISO639-2 format).')
|
||||
)
|
||||
setting_language_choices = namespace.add_setting(
|
||||
global_name='DOCUMENTS_LANGUAGE_CHOICES', default=LANGUAGE_CHOICES,
|
||||
help_text=_('List of supported document languages.')
|
||||
)
|
||||
|
||||
@@ -3,5 +3,7 @@ from __future__ import unicode_literals
|
||||
from django.dispatch import Signal
|
||||
|
||||
post_version_upload = Signal(providing_args=['instance'], use_caching=True)
|
||||
post_document_type_change = Signal(providing_args=['instance'], use_caching=True)
|
||||
post_document_type_change = Signal(
|
||||
providing_args=['instance'], use_caching=True
|
||||
)
|
||||
post_document_created = Signal(providing_args=['instance'], use_caching=True)
|
||||
|
||||
@@ -14,7 +14,9 @@ def get_used_size(path, file_list):
|
||||
total_size = 0
|
||||
for filename in file_list:
|
||||
try:
|
||||
total_size += storage_backend.size(storage_backend.separator.join([path, filename]))
|
||||
total_size += storage_backend.size(
|
||||
storage_backend.separator.join([path, filename])
|
||||
)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
@@ -45,11 +47,19 @@ class DocumentStatistics(Statistic):
|
||||
results.extend([
|
||||
_('Document types: %d') % DocumentType.objects.count(),
|
||||
])
|
||||
document_stats = DocumentVersion.objects.annotate(page_count=Count('pages')).aggregate(Min('page_count'), Max('page_count'), Avg('page_count'))
|
||||
document_stats = DocumentVersion.objects.annotate(
|
||||
page_count=Count('pages')
|
||||
).aggregate(Min('page_count'), Max('page_count'), Avg('page_count'))
|
||||
results.extend([
|
||||
_('Minimum amount of pages per document: %d') % (document_stats['page_count__min'] or 0),
|
||||
_('Maximum amount of pages per document: %d') % (document_stats['page_count__max'] or 0),
|
||||
_('Average amount of pages per document: %f') % (document_stats['page_count__avg'] or 0),
|
||||
_(
|
||||
'Minimum amount of pages per document: %d'
|
||||
) % (document_stats['page_count__min'] or 0),
|
||||
_(
|
||||
'Maximum amount of pages per document: %d'
|
||||
) % (document_stats['page_count__max'] or 0),
|
||||
_(
|
||||
'Average amount of pages per document: %f'
|
||||
) % (document_stats['page_count__avg'] or 0),
|
||||
])
|
||||
|
||||
return results
|
||||
@@ -69,16 +79,25 @@ class DocumentUsageStatistics(Statistic):
|
||||
total_storage_documents, storage_used_space = storage_count()
|
||||
results.append(_('Documents in storage: %d') %
|
||||
total_storage_documents)
|
||||
results.append(_('Space used in storage: %(base_2)s (base 2), %(base_10)s (base 10), %(bytes)d bytes') % {
|
||||
'base_2': pretty_size(storage_used_space),
|
||||
'base_10': pretty_size_10(storage_used_space),
|
||||
'bytes': storage_used_space
|
||||
})
|
||||
results.append(
|
||||
_(
|
||||
'Space used in storage: %(base_2)s (base 2), %(base_10)s '
|
||||
'(base 10), %(bytes)d bytes'
|
||||
) % {
|
||||
'base_2': pretty_size(storage_used_space),
|
||||
'base_10': pretty_size_10(storage_used_space),
|
||||
'bytes': storage_used_space
|
||||
}
|
||||
)
|
||||
except NotImplementedError:
|
||||
pass
|
||||
|
||||
results.extend([
|
||||
_('Document pages in database: %d') % DocumentPage.objects.only('pk',).count(),
|
||||
])
|
||||
results.extend(
|
||||
[
|
||||
_(
|
||||
'Document pages in database: %d'
|
||||
) % DocumentPage.objects.only('pk',).count(),
|
||||
]
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
@@ -27,16 +27,31 @@ def task_check_delete_periods():
|
||||
logger.info('Executing')
|
||||
|
||||
for document_type in DocumentType.objects.all():
|
||||
logger.info('Checking deletion period of document type: %s', document_type)
|
||||
logger.info(
|
||||
'Checking deletion period of document type: %s', document_type
|
||||
)
|
||||
if document_type.delete_time_period and document_type.delete_time_unit:
|
||||
delta = timedelta(**{document_type.delete_time_unit: document_type.delete_time_period})
|
||||
logger.info('Document type: %s, has a deletion period delta of: %s', document_type, delta)
|
||||
delta = timedelta(
|
||||
**{
|
||||
document_type.delete_time_unit: document_type.delete_time_period
|
||||
}
|
||||
)
|
||||
logger.info(
|
||||
'Document type: %s, has a deletion period delta of: %s',
|
||||
document_type, delta
|
||||
)
|
||||
for document in DeletedDocument.objects.filter(document_type=document_type):
|
||||
if now() > document.deleted_date_time + delta:
|
||||
logger.info('Document "%s" with id: %d, trashed on: %s, exceded delete period', document, document.pk, document.deleted_date_time)
|
||||
logger.info(
|
||||
'Document "%s" with id: %d, trashed on: %s, exceded '
|
||||
'delete period', document, document.pk,
|
||||
document.deleted_date_time
|
||||
)
|
||||
document.delete()
|
||||
else:
|
||||
logger.info('Document type: %s, has a no retention delta', document_type)
|
||||
logger.info(
|
||||
'Document type: %s, has a no retention delta', document_type
|
||||
)
|
||||
|
||||
logger.info('Finshed')
|
||||
|
||||
@@ -46,16 +61,31 @@ def task_check_trash_periods():
|
||||
logger.info('Executing')
|
||||
|
||||
for document_type in DocumentType.objects.all():
|
||||
logger.info('Checking trash period of document type: %s', document_type)
|
||||
logger.info(
|
||||
'Checking trash period of document type: %s', document_type
|
||||
)
|
||||
if document_type.trash_time_period and document_type.trash_time_unit:
|
||||
delta = timedelta(**{document_type.trash_time_unit: document_type.trash_time_period})
|
||||
logger.info('Document type: %s, has a trash period delta of: %s', document_type, delta)
|
||||
delta = timedelta(
|
||||
**{
|
||||
document_type.trash_time_unit: document_type.trash_time_period
|
||||
}
|
||||
)
|
||||
logger.info(
|
||||
'Document type: %s, has a trash period delta of: %s',
|
||||
document_type, delta
|
||||
)
|
||||
for document in Document.objects.filter(document_type=document_type):
|
||||
if now() > document.date_added + delta:
|
||||
logger.info('Document "%s" with id: %d, added on: %s, exceded trash period', document, document.pk, document.date_added)
|
||||
logger.info(
|
||||
'Document "%s" with id: %d, added on: %s, exceded '
|
||||
'trash period', document, document.pk,
|
||||
document.date_added
|
||||
)
|
||||
document.delete()
|
||||
else:
|
||||
logger.info('Document type: %s, has a no retention delta', document_type)
|
||||
logger.info(
|
||||
'Document type: %s, has a no retention delta', document_type
|
||||
)
|
||||
|
||||
logger.info('Finshed')
|
||||
|
||||
@@ -80,7 +110,11 @@ def task_update_page_count(self, version_id):
|
||||
try:
|
||||
document_version.update_page_count()
|
||||
except OperationalError as exception:
|
||||
logger.warning('Operational error during attempt to update page count for document version: %s; %s. Retrying.', document_version, exception)
|
||||
logger.warning(
|
||||
'Operational error during attempt to update page count for '
|
||||
'document version: %s; %s. Retrying.', document_version,
|
||||
exception
|
||||
)
|
||||
raise self.retry(exc=exception)
|
||||
|
||||
|
||||
@@ -88,63 +122,100 @@ def task_update_page_count(self, version_id):
|
||||
def task_upload_new_document(self, document_type_id, shared_uploaded_file_id, description=None, label=None, language=None, user_id=None):
|
||||
try:
|
||||
document_type = DocumentType.objects.get(pk=document_type_id)
|
||||
shared_file = SharedUploadedFile.objects.get(pk=shared_uploaded_file_id)
|
||||
shared_file = SharedUploadedFile.objects.get(
|
||||
pk=shared_uploaded_file_id
|
||||
)
|
||||
if user_id:
|
||||
user = User.objects.get(pk=user_id)
|
||||
else:
|
||||
user = None
|
||||
|
||||
except OperationalError as exception:
|
||||
logger.warning('Operational error during attempt to gather data for new document: %s; Retrying.', exception)
|
||||
logger.warning(
|
||||
'Operational error during attempt to gather data for new '
|
||||
'document: %s; Retrying.', exception
|
||||
)
|
||||
raise self.retry(exc=exception)
|
||||
|
||||
try:
|
||||
with shared_file.open as file_object:
|
||||
document_version = document_type.new_document(self, file_object=file_object, label=label, description=description, language=language, _user=user)
|
||||
document_version = document_type.new_document(
|
||||
self, file_object=file_object, label=label,
|
||||
description=description, language=language, _user=user
|
||||
)
|
||||
except OperationalError as exception:
|
||||
logger.warning('Operational error during attempt to gather data for new document: %s; Retrying.', exception)
|
||||
logger.warning(
|
||||
'Operational error during attempt to gather data for new '
|
||||
'document: %s; Retrying.', exception
|
||||
)
|
||||
raise self.retry(exc=exception)
|
||||
|
||||
try:
|
||||
shared_file.delete()
|
||||
except OperationalError as exception:
|
||||
logger.warning('Operational error while trying to delete shared file used to upload new document: %s; %s. Retrying.', document_version.document, exception)
|
||||
logger.warning(
|
||||
'Operational error while trying to delete shared file used to '
|
||||
'upload new document: %s; %s. Retrying.',
|
||||
document_version.document, exception
|
||||
)
|
||||
|
||||
|
||||
@app.task(bind=True, default_retry_delay=UPLOAD_NEW_VERSION_RETRY_DELAY, ignore_result=True)
|
||||
def task_upload_new_version(self, document_id, shared_uploaded_file_id, user_id, comment=None):
|
||||
try:
|
||||
document = Document.objects.get(pk=document_id)
|
||||
shared_file = SharedUploadedFile.objects.get(pk=shared_uploaded_file_id)
|
||||
shared_file = SharedUploadedFile.objects.get(
|
||||
pk=shared_uploaded_file_id
|
||||
)
|
||||
if user_id:
|
||||
user = User.objects.get(pk=user_id)
|
||||
else:
|
||||
user = None
|
||||
|
||||
except OperationalError as exception:
|
||||
logger.warning('Operational error during attempt to retrieve shared data for new document version for:%s; %s. Retrying.', document, exception)
|
||||
logger.warning(
|
||||
'Operational error during attempt to retrieve shared data for '
|
||||
'new document version for:%s; %s. Retrying.', document, exception
|
||||
)
|
||||
raise self.retry(exc=exception)
|
||||
|
||||
with shared_file.open() as file_object:
|
||||
document_version = DocumentVersion(document=document, comment=comment, file=file_object)
|
||||
document_version = DocumentVersion(
|
||||
document=document, comment=comment, file=file_object
|
||||
)
|
||||
try:
|
||||
document_version.save(_user=user)
|
||||
except Warning as warning:
|
||||
# New document version are blocked
|
||||
logger.info('Warning during attempt to create new document version for document: %s; %s', document, warning)
|
||||
logger.info(
|
||||
'Warning during attempt to create new document version for '
|
||||
'document: %s; %s', document, warning
|
||||
)
|
||||
shared_file.delete()
|
||||
except OperationalError as exception:
|
||||
logger.warning('Operational error during attempt to create new document version for document: %s; %s. Retrying.', document, exception)
|
||||
logger.warning(
|
||||
'Operational error during attempt to create new document '
|
||||
'version for document: %s; %s. Retrying.', document, exception
|
||||
)
|
||||
raise self.retry(exc=exception)
|
||||
except Exception as exception:
|
||||
# This except and else block emulate a finally:
|
||||
logger.error('Unexpected error during attempt to create new document version for document: %s; %s', document, exception)
|
||||
logger.error(
|
||||
'Unexpected error during attempt to create new document '
|
||||
'version for document: %s; %s', document, exception
|
||||
)
|
||||
try:
|
||||
shared_file.delete()
|
||||
except OperationalError as exception:
|
||||
logger.warning('Operational error during attempt to delete shared file: %s; %s.', shared_file, exception)
|
||||
logger.warning(
|
||||
'Operational error during attempt to delete shared '
|
||||
'file: %s; %s.', shared_file, exception
|
||||
)
|
||||
else:
|
||||
try:
|
||||
shared_file.delete()
|
||||
except OperationalError as exception:
|
||||
logger.warning('Operational error during attempt to delete shared file: %s; %s.', shared_file, exception)
|
||||
logger.warning(
|
||||
'Operational error during attempt to delete shared '
|
||||
'file: %s; %s.', shared_file, exception
|
||||
)
|
||||
|
||||
@@ -22,13 +22,18 @@ from .test_models import (
|
||||
|
||||
class DocumentAPICreateDocumentTestCase(TestCase):
|
||||
"""
|
||||
Functional test to make sure all the moving parts to create a document from
|
||||
the API are working correctly
|
||||
Functional test to make sure all the moving parts to create a document
|
||||
from the API are working correctly
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.admin_user = User.objects.create_superuser(username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL, password=TEST_ADMIN_PASSWORD)
|
||||
self.document_type = DocumentType.objects.create(label=TEST_DOCUMENT_TYPE)
|
||||
self.admin_user = User.objects.create_superuser(
|
||||
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
|
||||
password=TEST_ADMIN_PASSWORD
|
||||
)
|
||||
self.document_type = DocumentType.objects.create(
|
||||
label=TEST_DOCUMENT_TYPE
|
||||
)
|
||||
|
||||
ocr_settings = self.document_type.ocr_settings
|
||||
ocr_settings.auto_ocr = False
|
||||
@@ -41,7 +46,12 @@ class DocumentAPICreateDocumentTestCase(TestCase):
|
||||
def test_uploading_a_document_using_token_auth(self):
|
||||
# Get the an user token
|
||||
token_client = APIClient()
|
||||
response = token_client.post(reverse('auth_token_obtain'), {'username': TEST_ADMIN_USERNAME, 'password': TEST_ADMIN_PASSWORD})
|
||||
response = token_client.post(
|
||||
reverse('auth_token_obtain'), {
|
||||
'username': TEST_ADMIN_USERNAME,
|
||||
'password': TEST_ADMIN_PASSWORD
|
||||
}
|
||||
)
|
||||
|
||||
# Be able to get authentication token
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
@@ -66,17 +76,26 @@ class DocumentAPICreateDocumentTestCase(TestCase):
|
||||
|
||||
# Create a blank document
|
||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_descriptor:
|
||||
document_response = document_client.post(reverse('document-list'), {'document_type': self.document_type.pk, 'file': file_descriptor})
|
||||
document_response = document_client.post(
|
||||
reverse('document-list'), {
|
||||
'document_type': self.document_type.pk,
|
||||
'file': file_descriptor
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(document_response.status_code, status.HTTP_201_CREATED)
|
||||
|
||||
# The document was created in the DB?
|
||||
self.assertEqual(Document.objects.count(), 1)
|
||||
|
||||
new_version_url = reverse('document-new-version', args=[Document.objects.first().pk])
|
||||
new_version_url = reverse(
|
||||
'document-new-version', args=[Document.objects.first().pk]
|
||||
)
|
||||
|
||||
with open(TEST_DOCUMENT_PATH) as file_descriptor:
|
||||
response = document_client.post(new_version_url, {'file': file_descriptor})
|
||||
response = document_client.post(
|
||||
new_version_url, {'file': file_descriptor}
|
||||
)
|
||||
|
||||
# Make sure the document uploaded correctly
|
||||
document = Document.objects.first()
|
||||
@@ -86,13 +105,20 @@ class DocumentAPICreateDocumentTestCase(TestCase):
|
||||
self.assertEqual(document.file_mimetype, 'application/pdf')
|
||||
self.assertEqual(document.file_mime_encoding, 'binary')
|
||||
self.assertEqual(document.label, TEST_SMALL_DOCUMENT_FILENAME)
|
||||
self.assertEqual(document.checksum, 'c637ffab6b8bb026ed3784afdb07663fddc60099853fae2be93890852a69ecf3')
|
||||
self.assertEqual(
|
||||
document.checksum,
|
||||
'c637ffab6b8bb026ed3784afdb07663fddc60099853fae2be93890852a69ecf3'
|
||||
)
|
||||
self.assertEqual(document.page_count, 47)
|
||||
|
||||
# Make sure we can edit the document via the API
|
||||
document_url = reverse('document-detail', args=[Document.objects.first().pk])
|
||||
document_url = reverse(
|
||||
'document-detail', args=[Document.objects.first().pk]
|
||||
)
|
||||
|
||||
response = document_client.post(document_url, {'description': 'edited test document'})
|
||||
response = document_client.post(
|
||||
document_url, {'description': 'edited test document'}
|
||||
)
|
||||
|
||||
# self.assertTrue(document.description, 'edited test document')
|
||||
|
||||
|
||||
@@ -17,26 +17,47 @@ TEST_COMPRESSED_DOCUMENTS_FILENAME = 'compressed_documents.zip'
|
||||
TEST_SMALL_DOCUMENT_FILENAME = 'title_page.png'
|
||||
TEST_NON_ASCII_DOCUMENT_FILENAME = 'I18N_title_áéíóúüñÑ.png'
|
||||
TEST_NON_ASCII_COMPRESSED_DOCUMENT_FILENAME = 'I18N_title_áéíóúüñÑ.png.zip'
|
||||
TEST_DOCUMENT_PATH = os.path.join(settings.BASE_DIR, 'contrib', 'sample_documents', 'mayan_11_1.pdf')
|
||||
TEST_SMALL_DOCUMENT_PATH = os.path.join(settings.BASE_DIR, 'contrib', 'sample_documents', TEST_SMALL_DOCUMENT_FILENAME)
|
||||
TEST_NON_ASCII_DOCUMENT_PATH = os.path.join(settings.BASE_DIR, 'contrib', 'sample_documents', TEST_NON_ASCII_DOCUMENT_FILENAME)
|
||||
TEST_NON_ASCII_COMPRESSED_DOCUMENT_PATH = os.path.join(settings.BASE_DIR, 'contrib', 'sample_documents', TEST_NON_ASCII_COMPRESSED_DOCUMENT_FILENAME)
|
||||
TEST_DEU_DOCUMENT_PATH = os.path.join(settings.BASE_DIR, 'contrib', 'sample_documents', TEST_DEU_DOCUMENT_FILENAME)
|
||||
TEST_COMPRESSED_DOCUMENT_PATH = os.path.join(settings.BASE_DIR, 'contrib', 'sample_documents', TEST_COMPRESSED_DOCUMENTS_FILENAME)
|
||||
TEST_DOCUMENT_PATH = os.path.join(
|
||||
settings.BASE_DIR, 'contrib', 'sample_documents', 'mayan_11_1.pdf'
|
||||
)
|
||||
TEST_SMALL_DOCUMENT_PATH = os.path.join(
|
||||
settings.BASE_DIR, 'contrib', 'sample_documents',
|
||||
TEST_SMALL_DOCUMENT_FILENAME
|
||||
)
|
||||
TEST_NON_ASCII_DOCUMENT_PATH = os.path.join(
|
||||
settings.BASE_DIR, 'contrib', 'sample_documents',
|
||||
TEST_NON_ASCII_DOCUMENT_FILENAME
|
||||
)
|
||||
TEST_NON_ASCII_COMPRESSED_DOCUMENT_PATH = os.path.join(
|
||||
settings.BASE_DIR, 'contrib', 'sample_documents',
|
||||
TEST_NON_ASCII_COMPRESSED_DOCUMENT_FILENAME
|
||||
)
|
||||
TEST_DEU_DOCUMENT_PATH = os.path.join(
|
||||
settings.BASE_DIR, 'contrib', 'sample_documents',
|
||||
TEST_DEU_DOCUMENT_FILENAME
|
||||
)
|
||||
TEST_COMPRESSED_DOCUMENT_PATH = os.path.join(
|
||||
settings.BASE_DIR, 'contrib', 'sample_documents',
|
||||
TEST_COMPRESSED_DOCUMENTS_FILENAME
|
||||
)
|
||||
TEST_DOCUMENT_DESCRIPTION = 'test description'
|
||||
TEST_DOCUMENT_TYPE = 'test_document_type'
|
||||
|
||||
|
||||
class DocumentTestCase(TestCase):
|
||||
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.auto_ocr = False
|
||||
ocr_settings.save()
|
||||
|
||||
with open(TEST_DOCUMENT_PATH) as file_object:
|
||||
self.document = self.document_type.new_document(file_object=File(file_object), label='mayan_11_1.pdf')
|
||||
self.document = self.document_type.new_document(
|
||||
file_object=File(file_object), label='mayan_11_1.pdf'
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
self.document_type.delete()
|
||||
@@ -50,7 +71,10 @@ class DocumentTestCase(TestCase):
|
||||
self.assertEqual(self.document.file_mimetype, 'application/pdf')
|
||||
self.assertEqual(self.document.file_mime_encoding, 'binary')
|
||||
self.assertEqual(self.document.label, 'mayan_11_1.pdf')
|
||||
self.assertEqual(self.document.checksum, 'c637ffab6b8bb026ed3784afdb07663fddc60099853fae2be93890852a69ecf3')
|
||||
self.assertEqual(
|
||||
self.document.checksum,
|
||||
'c637ffab6b8bb026ed3784afdb07663fddc60099853fae2be93890852a69ecf3'
|
||||
)
|
||||
self.assertEqual(self.document.page_count, 47)
|
||||
|
||||
def test_version_creation(self):
|
||||
@@ -58,7 +82,9 @@ class DocumentTestCase(TestCase):
|
||||
self.document.new_version(file_object=File(file_object))
|
||||
|
||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
||||
self.document.new_version(file_object=File(file_object), comment='test comment 1')
|
||||
self.document.new_version(
|
||||
file_object=File(file_object), comment='test comment 1'
|
||||
)
|
||||
|
||||
self.assertEqual(self.document.versions.count(), 3)
|
||||
|
||||
|
||||
@@ -22,20 +22,29 @@ class DocumentsViewsFunctionalTestCase(TestCase):
|
||||
"""
|
||||
|
||||
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.auto_ocr = False
|
||||
ocr_settings.save()
|
||||
|
||||
self.admin_user = User.objects.create_superuser(username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL, password=TEST_ADMIN_PASSWORD)
|
||||
self.admin_user = User.objects.create_superuser(
|
||||
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
|
||||
password=TEST_ADMIN_PASSWORD
|
||||
)
|
||||
self.client = Client()
|
||||
# Login the admin user
|
||||
logged_in = self.client.login(username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD)
|
||||
logged_in = self.client.login(
|
||||
username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD
|
||||
)
|
||||
self.assertTrue(logged_in)
|
||||
self.assertTrue(self.admin_user.is_authenticated())
|
||||
|
||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
||||
self.document = self.document_type.new_document(file_object=File(file_object), label='mayan_11_1.pdf')
|
||||
self.document = self.document_type.new_document(
|
||||
file_object=File(file_object), label='mayan_11_1.pdf'
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
self.document_type.delete()
|
||||
@@ -45,12 +54,16 @@ class DocumentsViewsFunctionalTestCase(TestCase):
|
||||
self.assertEqual(Document.objects.count(), 1)
|
||||
|
||||
# Trash the document
|
||||
self.client.post(reverse('documents:document_trash', args=[self.document.pk]))
|
||||
self.client.post(
|
||||
reverse('documents:document_trash', args=[self.document.pk])
|
||||
)
|
||||
self.assertEqual(DeletedDocument.objects.count(), 1)
|
||||
self.assertEqual(Document.objects.count(), 0)
|
||||
|
||||
# Restore the document
|
||||
self.client.post(reverse('documents:document_restore', args=[self.document.pk]))
|
||||
self.client.post(
|
||||
reverse('documents:document_restore', args=[self.document.pk])
|
||||
)
|
||||
self.assertEqual(DeletedDocument.objects.count(), 0)
|
||||
self.assertEqual(Document.objects.count(), 1)
|
||||
|
||||
@@ -58,12 +71,16 @@ class DocumentsViewsFunctionalTestCase(TestCase):
|
||||
self.assertEqual(Document.objects.count(), 1)
|
||||
|
||||
# Trash the document
|
||||
self.client.post(reverse('documents:document_trash', args=[self.document.pk]))
|
||||
self.client.post(
|
||||
reverse('documents:document_trash', args=[self.document.pk])
|
||||
)
|
||||
self.assertEqual(DeletedDocument.objects.count(), 1)
|
||||
self.assertEqual(Document.objects.count(), 0)
|
||||
|
||||
# Delete the document
|
||||
self.client.post(reverse('documents:document_delete', args=[self.document.pk]))
|
||||
self.client.post(
|
||||
reverse('documents:document_delete', args=[self.document.pk])
|
||||
)
|
||||
self.assertEqual(DeletedDocument.objects.count(), 0)
|
||||
self.assertEqual(Document.objects.count(), 0)
|
||||
|
||||
@@ -72,8 +89,12 @@ class DocumentsViewsFunctionalTestCase(TestCase):
|
||||
self.assertContains(response, 'Total: 1', status_code=200)
|
||||
|
||||
# test document simple view
|
||||
response = self.client.get(reverse('documents:document_properties', args=[self.document.pk]))
|
||||
self.assertContains(response, 'roperties for document', status_code=200)
|
||||
response = self.client.get(
|
||||
reverse('documents:document_properties', args=[self.document.pk])
|
||||
)
|
||||
self.assertContains(
|
||||
response, 'roperties for document', status_code=200
|
||||
)
|
||||
|
||||
def test_document_type_views(self):
|
||||
# Check that there are no document types
|
||||
@@ -81,8 +102,12 @@ class DocumentsViewsFunctionalTestCase(TestCase):
|
||||
self.assertContains(response, 'Total: 1', status_code=200)
|
||||
|
||||
# Create a document type
|
||||
response = self.client.post(reverse('documents:document_type_create'), {'name': 'test document type 2'}, follow=True)
|
||||
#TODO: FIX self.assertContains(response, 'successfully', status_code=200)
|
||||
response = self.client.post(
|
||||
reverse('documents:document_type_create'),
|
||||
{'name': 'test document type 2'}, follow=True
|
||||
)
|
||||
#TODO: FIX
|
||||
# self.assertContains(response, 'successfully', status_code=200)
|
||||
|
||||
# Check that there are two document types
|
||||
response = self.client.get(reverse('documents:document_type_list'))
|
||||
@@ -91,18 +116,42 @@ class DocumentsViewsFunctionalTestCase(TestCase):
|
||||
self.assertEqual(self.document_type.label, TEST_DOCUMENT_TYPE)
|
||||
|
||||
# Edit the document type
|
||||
response = self.client.post(reverse('documents:document_type_edit', args=[self.document_type.pk]), data={'name': TEST_DOCUMENT_TYPE + 'partial'}, follow=True)
|
||||
#TODO: FIX self.assertContains(response, 'Document type edited successfully', status_code=200)
|
||||
response = self.client.post(
|
||||
reverse(
|
||||
'documents:document_type_edit', args=[self.document_type.pk]
|
||||
), data={'name': TEST_DOCUMENT_TYPE + 'partial'}, follow=True
|
||||
)
|
||||
#TODO: FIX
|
||||
# self.assertContains(
|
||||
# response, 'Document type edited successfully', status_code=200
|
||||
#)
|
||||
|
||||
# Reload document type model data
|
||||
#self.document_type = DocumentType.objects.get(pk=self.document_type.pk)
|
||||
#TODO: FIX self.assertEqual(self.document_type.name, TEST_DOCUMENT_TYPE + 'partial')
|
||||
#self.document_type = DocumentType.objects.get(
|
||||
# pk=self.document_type.pk
|
||||
#)
|
||||
#TODO: FIX#
|
||||
#self.assertEqual(
|
||||
# self.document_type.name, TEST_DOCUMENT_TYPE + 'partial'
|
||||
#)
|
||||
|
||||
# Delete the document type
|
||||
response = self.client.post(reverse('documents:document_type_delete', args=[self.document_type.pk]), follow=True)
|
||||
#TODO: FIX self.assertContains(response, 'Document type: {0} deleted successfully'.format(self.document_type.name), status_code=200)
|
||||
#response = self.client.post(
|
||||
# reverse(
|
||||
# 'documents:document_type_delete', args=[self.document_type.pk]
|
||||
# ), follow=True
|
||||
#)
|
||||
#TODO: FIX#
|
||||
# self.assertContains(
|
||||
# response, 'Document type: {0} deleted successfully'.format(
|
||||
# self.document_type.name
|
||||
# ), status_code=200
|
||||
#)
|
||||
|
||||
# Check that there are no document types
|
||||
response = self.client.get(reverse('documents:document_type_list'))
|
||||
#response = self.client.get(reverse('documents:document_type_list'))
|
||||
#TODO: FIX self.assertEqual(response.status_code, 200)
|
||||
#TODO: FIX self.assertContains(response, 'ocument types (0)', status_code=200)
|
||||
#TODO: FIX
|
||||
#self.assertContains(
|
||||
# response, 'ocument types (0)', status_code=200
|
||||
#)
|
||||
|
||||
@@ -21,74 +21,250 @@ from .views import (
|
||||
urlpatterns = patterns(
|
||||
'documents.views',
|
||||
url(r'^list/$', DocumentListView.as_view(), name='document_list'),
|
||||
url(r'^list/recent/$', RecentDocumentListView.as_view(), name='document_list_recent'),
|
||||
url(r'^list/deleted/$', DeletedDocumentListView.as_view(), name='document_list_deleted'),
|
||||
url(
|
||||
r'^list/recent/$', RecentDocumentListView.as_view(),
|
||||
name='document_list_recent'
|
||||
),
|
||||
url(
|
||||
r'^list/deleted/$', DeletedDocumentListView.as_view(),
|
||||
name='document_list_deleted'
|
||||
),
|
||||
|
||||
url(r'^(?P<document_id>\d+)/preview/$', 'document_preview', name='document_preview'),
|
||||
url(r'^(?P<document_id>\d+)/properties/$', 'document_properties', name='document_properties'),
|
||||
url(r'^(?P<pk>\d+)/restore/$', DocumentRestoreView.as_view(), name='document_restore'),
|
||||
url(r'^multiple/restore/$', DocumentManyRestoreView.as_view(), name='document_multiple_restore'),
|
||||
url(r'^(?P<pk>\d+)/delete/$', DeletedDocumentDeleteView.as_view(), name='document_delete'),
|
||||
url(r'^multiple/delete/$', DocumentManyDeleteView.as_view(), name='document_multiple_delete'),
|
||||
url(r'^(?P<document_id>\d+)/type/$', 'document_document_type_edit', name='document_document_type_edit'),
|
||||
url(r'^multiple/type/$', 'document_multiple_document_type_edit', name='document_multiple_document_type_edit'),
|
||||
url(r'^(?P<document_id>\d+)/trash/$', 'document_trash', name='document_trash'),
|
||||
url(r'^multiple/trash/$', 'document_multiple_trash', name='document_multiple_trash'),
|
||||
url(r'^(?P<document_id>\d+)/edit/$', 'document_edit', name='document_edit'),
|
||||
url(r'^(?P<document_id>\d+)/print/$', 'document_print', name='document_print'),
|
||||
url(r'^(?P<document_id>\d+)/reset_page_count/$', 'document_update_page_count', name='document_update_page_count'),
|
||||
url(r'^multiple/reset_page_count/$', 'document_multiple_update_page_count', name='document_multiple_update_page_count'),
|
||||
url(
|
||||
r'^(?P<document_id>\d+)/preview/$', 'document_preview',
|
||||
name='document_preview'
|
||||
),
|
||||
url(
|
||||
r'^(?P<document_id>\d+)/properties/$', 'document_properties',
|
||||
name='document_properties'
|
||||
),
|
||||
url(
|
||||
r'^(?P<pk>\d+)/restore/$', DocumentRestoreView.as_view(),
|
||||
name='document_restore'
|
||||
),
|
||||
url(
|
||||
r'^multiple/restore/$', DocumentManyRestoreView.as_view(),
|
||||
name='document_multiple_restore'
|
||||
),
|
||||
url(
|
||||
r'^(?P<pk>\d+)/delete/$', DeletedDocumentDeleteView.as_view(),
|
||||
name='document_delete'
|
||||
),
|
||||
url(
|
||||
r'^multiple/delete/$', DocumentManyDeleteView.as_view(),
|
||||
name='document_multiple_delete'
|
||||
),
|
||||
url(
|
||||
r'^(?P<document_id>\d+)/type/$', 'document_document_type_edit',
|
||||
name='document_document_type_edit'
|
||||
),
|
||||
url(
|
||||
r'^multiple/type/$', 'document_multiple_document_type_edit',
|
||||
name='document_multiple_document_type_edit'
|
||||
),
|
||||
url(
|
||||
r'^(?P<document_id>\d+)/trash/$', 'document_trash',
|
||||
name='document_trash'
|
||||
),
|
||||
url(
|
||||
r'^multiple/trash/$', 'document_multiple_trash',
|
||||
name='document_multiple_trash'
|
||||
),
|
||||
url(
|
||||
r'^(?P<document_id>\d+)/edit/$', 'document_edit',
|
||||
name='document_edit'
|
||||
),
|
||||
url(
|
||||
r'^(?P<document_id>\d+)/print/$', 'document_print',
|
||||
name='document_print'
|
||||
),
|
||||
url(
|
||||
r'^(?P<document_id>\d+)/reset_page_count/$',
|
||||
'document_update_page_count', name='document_update_page_count'
|
||||
),
|
||||
url(
|
||||
r'^multiple/reset_page_count/$',
|
||||
'document_multiple_update_page_count',
|
||||
name='document_multiple_update_page_count'
|
||||
),
|
||||
|
||||
url(r'^(?P<document_id>\d+)/display/$', 'get_document_image', {'size': setting_display_size.value}, 'document_display'),
|
||||
url(r'^(?P<document_id>\d+)/display/print/$', 'get_document_image', {'size': setting_print_size.value}, 'document_display_print'),
|
||||
url(
|
||||
r'^(?P<document_id>\d+)/display/$', 'get_document_image', {
|
||||
'size': setting_display_size.value
|
||||
}, 'document_display'
|
||||
),
|
||||
url(
|
||||
r'^(?P<document_id>\d+)/display/print/$', 'get_document_image', {
|
||||
'size': setting_print_size.value
|
||||
}, 'document_display_print'
|
||||
),
|
||||
|
||||
url(r'^(?P<document_id>\d+)/download/$', 'document_download', name='document_download'),
|
||||
url(r'^multiple/download/$', 'document_multiple_download', name='document_multiple_download'),
|
||||
url(r'^(?P<document_id>\d+)/clear_transformations/$', 'document_clear_transformations', name='document_clear_transformations'),
|
||||
url(
|
||||
r'^(?P<document_id>\d+)/download/$', 'document_download',
|
||||
name='document_download'
|
||||
),
|
||||
url(
|
||||
r'^multiple/download/$', 'document_multiple_download',
|
||||
name='document_multiple_download'
|
||||
),
|
||||
url(
|
||||
r'^(?P<document_id>\d+)/clear_transformations/$',
|
||||
'document_clear_transformations',
|
||||
name='document_clear_transformations'
|
||||
),
|
||||
|
||||
url(r'^(?P<document_pk>\d+)/version/all/$', 'document_version_list', name='document_version_list'),
|
||||
url(r'^document/version/(?P<document_version_pk>\d+)/download/$', 'document_download', name='document_version_download'),
|
||||
url(r'^document/version/(?P<document_version_pk>\d+)/revert/$', 'document_version_revert', name='document_version_revert'),
|
||||
url(
|
||||
r'^(?P<document_pk>\d+)/version/all/$', 'document_version_list',
|
||||
name='document_version_list'
|
||||
),
|
||||
url(
|
||||
r'^document/version/(?P<document_version_pk>\d+)/download/$',
|
||||
'document_download', name='document_version_download'
|
||||
),
|
||||
url(
|
||||
r'^document/version/(?P<document_version_pk>\d+)/revert/$',
|
||||
'document_version_revert', name='document_version_revert'
|
||||
),
|
||||
|
||||
url(r'^(?P<pk>\d+)/pages/all/$', DocumentPageListView.as_view(), name='document_pages'),
|
||||
url(
|
||||
r'^(?P<pk>\d+)/pages/all/$', DocumentPageListView.as_view(),
|
||||
name='document_pages'
|
||||
),
|
||||
|
||||
url(r'^multiple/clear_transformations/$', 'document_multiple_clear_transformations', name='document_multiple_clear_transformations'),
|
||||
url(r'^cache/clear/$', 'document_clear_image_cache', name='document_clear_image_cache'),
|
||||
url(r'^trash_can/empty/$', EmptyTrashCanView.as_view(), name='trash_can_empty'),
|
||||
url(
|
||||
r'^multiple/clear_transformations/$',
|
||||
'document_multiple_clear_transformations',
|
||||
name='document_multiple_clear_transformations'
|
||||
),
|
||||
url(
|
||||
r'^cache/clear/$', 'document_clear_image_cache',
|
||||
name='document_clear_image_cache'
|
||||
),
|
||||
url(
|
||||
r'^trash_can/empty/$', EmptyTrashCanView.as_view(),
|
||||
name='trash_can_empty'
|
||||
),
|
||||
|
||||
url(r'^page/(?P<document_page_id>\d+)/$', 'document_page_view', name='document_page_view'),
|
||||
url(r'^page/(?P<document_page_id>\d+)/navigation/next/$', 'document_page_navigation_next', name='document_page_navigation_next'),
|
||||
url(r'^page/(?P<document_page_id>\d+)/navigation/previous/$', 'document_page_navigation_previous', name='document_page_navigation_previous'),
|
||||
url(r'^page/(?P<document_page_id>\d+)/navigation/first/$', 'document_page_navigation_first', name='document_page_navigation_first'),
|
||||
url(r'^page/(?P<document_page_id>\d+)/navigation/last/$', 'document_page_navigation_last', name='document_page_navigation_last'),
|
||||
url(r'^page/(?P<document_page_id>\d+)/zoom/in/$', 'document_page_zoom_in', name='document_page_zoom_in'),
|
||||
url(r'^page/(?P<document_page_id>\d+)/zoom/out/$', 'document_page_zoom_out', name='document_page_zoom_out'),
|
||||
url(r'^page/(?P<document_page_id>\d+)/rotate/right/$', 'document_page_rotate_right', name='document_page_rotate_right'),
|
||||
url(r'^page/(?P<document_page_id>\d+)/rotate/left/$', 'document_page_rotate_left', name='document_page_rotate_left'),
|
||||
url(r'^page/(?P<document_page_id>\d+)/reset/$', 'document_page_view_reset', name='document_page_view_reset'),
|
||||
url(
|
||||
r'^page/(?P<document_page_id>\d+)/$', 'document_page_view',
|
||||
name='document_page_view'
|
||||
),
|
||||
url(
|
||||
r'^page/(?P<document_page_id>\d+)/navigation/next/$',
|
||||
'document_page_navigation_next', name='document_page_navigation_next'
|
||||
),
|
||||
url(
|
||||
r'^page/(?P<document_page_id>\d+)/navigation/previous/$',
|
||||
'document_page_navigation_previous',
|
||||
name='document_page_navigation_previous'
|
||||
),
|
||||
url(
|
||||
r'^page/(?P<document_page_id>\d+)/navigation/first/$',
|
||||
'document_page_navigation_first', name='document_page_navigation_first'
|
||||
),
|
||||
url(
|
||||
r'^page/(?P<document_page_id>\d+)/navigation/last/$',
|
||||
'document_page_navigation_last', name='document_page_navigation_last'
|
||||
),
|
||||
url(
|
||||
r'^page/(?P<document_page_id>\d+)/zoom/in/$',
|
||||
'document_page_zoom_in', name='document_page_zoom_in'
|
||||
),
|
||||
url(
|
||||
r'^page/(?P<document_page_id>\d+)/zoom/out/$',
|
||||
'document_page_zoom_out', name='document_page_zoom_out'
|
||||
),
|
||||
url(
|
||||
r'^page/(?P<document_page_id>\d+)/rotate/right/$',
|
||||
'document_page_rotate_right', name='document_page_rotate_right'
|
||||
),
|
||||
url(
|
||||
r'^page/(?P<document_page_id>\d+)/rotate/left/$',
|
||||
'document_page_rotate_left', name='document_page_rotate_left'
|
||||
),
|
||||
url(
|
||||
r'^page/(?P<document_page_id>\d+)/reset/$',
|
||||
'document_page_view_reset', name='document_page_view_reset'
|
||||
),
|
||||
|
||||
# Admin views
|
||||
url(r'^type/list/$', DocumentTypeListView.as_view(), name='document_type_list'),
|
||||
url(r'^type/create/$', DocumentTypeCreateView.as_view(), name='document_type_create'),
|
||||
url(r'^type/(?P<pk>\d+)/edit/$', DocumentTypeEditView.as_view(), name='document_type_edit'),
|
||||
url(r'^type/(?P<pk>\d+)/delete/$', DocumentTypeDeleteView.as_view(), name='document_type_delete'),
|
||||
url(r'^type/(?P<pk>\d+)/documents/$', DocumentTypeDocumentListView.as_view(), name='document_type_document_list'),
|
||||
url(r'^type/(?P<document_type_id>\d+)/filename/list/$', 'document_type_filename_list', name='document_type_filename_list'),
|
||||
url(r'^type/filename/(?P<document_type_filename_id>\d+)/edit/$', 'document_type_filename_edit', name='document_type_filename_edit'),
|
||||
url(r'^type/filename/(?P<document_type_filename_id>\d+)/delete/$', 'document_type_filename_delete', name='document_type_filename_delete'),
|
||||
url(r'^type/(?P<document_type_id>\d+)/filename/create/$', 'document_type_filename_create', name='document_type_filename_create'),
|
||||
url(
|
||||
r'^type/list/$', DocumentTypeListView.as_view(),
|
||||
name='document_type_list'
|
||||
),
|
||||
url(
|
||||
r'^type/create/$', DocumentTypeCreateView.as_view(),
|
||||
name='document_type_create'
|
||||
),
|
||||
url(
|
||||
r'^type/(?P<pk>\d+)/edit/$', DocumentTypeEditView.as_view(),
|
||||
name='document_type_edit'
|
||||
),
|
||||
url(
|
||||
r'^type/(?P<pk>\d+)/delete/$', DocumentTypeDeleteView.as_view(),
|
||||
name='document_type_delete'
|
||||
),
|
||||
url(
|
||||
r'^type/(?P<pk>\d+)/documents/$',
|
||||
DocumentTypeDocumentListView.as_view(),
|
||||
name='document_type_document_list'
|
||||
),
|
||||
url(
|
||||
r'^type/(?P<document_type_id>\d+)/filename/list/$',
|
||||
'document_type_filename_list', name='document_type_filename_list'
|
||||
),
|
||||
url(
|
||||
r'^type/filename/(?P<document_type_filename_id>\d+)/edit/$',
|
||||
'document_type_filename_edit', name='document_type_filename_edit'
|
||||
),
|
||||
url(
|
||||
r'^type/filename/(?P<document_type_filename_id>\d+)/delete/$',
|
||||
'document_type_filename_delete', name='document_type_filename_delete'
|
||||
),
|
||||
url(
|
||||
r'^type/(?P<document_type_id>\d+)/filename/create/$',
|
||||
'document_type_filename_create', name='document_type_filename_create'
|
||||
),
|
||||
)
|
||||
|
||||
api_urls = patterns(
|
||||
'',
|
||||
url(r'^documents/$', APIDocumentListView.as_view(), name='document-list'),
|
||||
url(r'^documents/recent/$', APIRecentDocumentListView.as_view(), name='document-recent-list'),
|
||||
url(r'^documents/(?P<pk>[0-9]+)/$', APIDocumentView.as_view(), name='document-detail'),
|
||||
url(r'^document_version/(?P<pk>[0-9]+)/$', APIDocumentVersionView.as_view(), name='documentversion-detail'),
|
||||
url(r'^document_page/(?P<pk>[0-9]+)/$', APIDocumentPageView.as_view(), name='documentpage-detail'),
|
||||
url(r'^documents/(?P<pk>[0-9]+)/image/$', APIDocumentImageView.as_view(), name='document-image'),
|
||||
url(r'^documents/(?P<pk>[0-9]+)/new_version/$', APIDocumentVersionCreateView.as_view(), name='document-new-version'),
|
||||
url(r'^documenttypes/(?P<pk>[0-9]+)/documents/$', APIDocumentTypeDocumentListView.as_view(), name='documenttype-document-list'),
|
||||
url(r'^documenttypes/(?P<pk>[0-9]+)/$', APIDocumentTypeView.as_view(), name='documenttype-detail'),
|
||||
url(r'^documenttypes/$', APIDocumentTypeListView.as_view(), name='documenttype-list'),
|
||||
url(
|
||||
r'^documents/recent/$', APIRecentDocumentListView.as_view(),
|
||||
name='document-recent-list'
|
||||
),
|
||||
url(
|
||||
r'^documents/(?P<pk>[0-9]+)/$', APIDocumentView.as_view(),
|
||||
name='document-detail'
|
||||
),
|
||||
url(
|
||||
r'^document_version/(?P<pk>[0-9]+)/$',
|
||||
APIDocumentVersionView.as_view(), name='documentversion-detail'
|
||||
),
|
||||
url(
|
||||
r'^document_page/(?P<pk>[0-9]+)/$', APIDocumentPageView.as_view(),
|
||||
name='documentpage-detail'
|
||||
),
|
||||
url(
|
||||
r'^documents/(?P<pk>[0-9]+)/image/$', APIDocumentImageView.as_view(),
|
||||
name='document-image'
|
||||
),
|
||||
url(
|
||||
r'^documents/(?P<pk>[0-9]+)/new_version/$',
|
||||
APIDocumentVersionCreateView.as_view(), name='document-new-version'
|
||||
),
|
||||
url(
|
||||
r'^documenttypes/(?P<pk>[0-9]+)/documents/$',
|
||||
APIDocumentTypeDocumentListView.as_view(),
|
||||
name='documenttype-document-list'
|
||||
),
|
||||
url(
|
||||
r'^documenttypes/(?P<pk>[0-9]+)/$', APIDocumentTypeView.as_view(),
|
||||
name='documenttype-detail'
|
||||
),
|
||||
url(
|
||||
r'^documenttypes/$', APIDocumentTypeListView.as_view(),
|
||||
name='documenttype-list'
|
||||
),
|
||||
)
|
||||
|
||||
@@ -2,7 +2,8 @@ from __future__ import unicode_literals
|
||||
|
||||
|
||||
def parse_range(astr):
|
||||
# http://stackoverflow.com/questions/4248399/page-range-for-printing-algorithm
|
||||
# http://stackoverflow.com/questions/4248399/
|
||||
# page-range-for-printing-algorithm
|
||||
result = set()
|
||||
for part in astr.split(','):
|
||||
x = part.split('-')
|
||||
|
||||
@@ -41,8 +41,8 @@ from .forms import (
|
||||
)
|
||||
from .literals import DOCUMENT_IMAGE_TASK_TIMEOUT
|
||||
from .models import (
|
||||
DeletedDocument, Document, DocumentType, DocumentPage, DocumentTypeFilename,
|
||||
DocumentVersion, RecentDocument
|
||||
DeletedDocument, Document, DocumentType, DocumentPage,
|
||||
DocumentTypeFilename, DocumentVersion, RecentDocument
|
||||
)
|
||||
from .permissions import (
|
||||
permission_document_delete, permission_document_download,
|
||||
@@ -94,11 +94,17 @@ class DeletedDocumentListView(DocumentListView):
|
||||
queryset = Document.trash.all()
|
||||
|
||||
try:
|
||||
Permission.check_permissions(self.request.user, [permission_document_view])
|
||||
Permission.check_permissions(
|
||||
self.request.user, [permission_document_view]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_document_view, self.request.user, queryset)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_document_view, self.request.user, queryset
|
||||
)
|
||||
|
||||
return DeletedDocument.objects.filter(pk__in=queryset.values_list('pk', flat=True))
|
||||
return DeletedDocument.objects.filter(
|
||||
pk__in=queryset.values_list('pk', flat=True)
|
||||
)
|
||||
|
||||
|
||||
class DeletedDocumentDeleteView(ConfirmView):
|
||||
@@ -107,12 +113,18 @@ class DeletedDocumentDeleteView(ConfirmView):
|
||||
}
|
||||
|
||||
def object_action(self, request, instance):
|
||||
source_document = get_object_or_404(Document.passthrough, pk=instance.pk)
|
||||
source_document = get_object_or_404(
|
||||
Document.passthrough, pk=instance.pk
|
||||
)
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_document_delete])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_document_delete]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_document_delete, request.user, source_document)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_document_delete, request.user, source_document
|
||||
)
|
||||
|
||||
instance.delete()
|
||||
messages.success(request, _('Document: %(document)s deleted.') % {
|
||||
@@ -132,16 +144,24 @@ class DocumentRestoreView(ConfirmView):
|
||||
}
|
||||
|
||||
def object_action(self, request, instance):
|
||||
source_document = get_object_or_404(Document.passthrough, pk=instance.pk)
|
||||
source_document = get_object_or_404(
|
||||
Document.passthrough, pk=instance.pk
|
||||
)
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_document_restore])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_document_restore]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_document_restore, request.user, source_document)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_document_restore, request.user, source_document
|
||||
)
|
||||
|
||||
instance.restore()
|
||||
messages.success(request, _('Document: %(document)s restored.') % {
|
||||
'document': instance}
|
||||
messages.success(
|
||||
request, _('Document: %(document)s restored.') % {
|
||||
'document': instance
|
||||
}
|
||||
)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
@@ -204,7 +224,9 @@ class EmptyTrashCanView(ConfirmView):
|
||||
'title': _('Empty trash?')
|
||||
}
|
||||
view_permission = permission_empty_trash
|
||||
action_cancel_redirect = post_action_redirect = reverse_lazy('documents:document_list_deleted')
|
||||
action_cancel_redirect = post_action_redirect = reverse_lazy(
|
||||
'documents:document_list_deleted'
|
||||
)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
for deleted_document in DeletedDocument.objects.all():
|
||||
@@ -231,27 +253,43 @@ def document_properties(request, document_id):
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_document_view])
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_document_view, request.user, document)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_document_view, request.user, document
|
||||
)
|
||||
|
||||
document.add_as_recent_document_for_user(request.user)
|
||||
|
||||
document_fields = [
|
||||
{'label': _('Date added'), 'field': lambda x: x.date_added.date()},
|
||||
{'label': _('Time added'), 'field': lambda x: unicode(x.date_added.time()).split('.')[0]},
|
||||
{
|
||||
'label': _('Time added'),
|
||||
'field': lambda x: unicode(x.date_added.time()).split('.')[0]
|
||||
},
|
||||
{'label': _('UUID'), 'field': 'uuid'},
|
||||
]
|
||||
if document.latest_version:
|
||||
document_fields.extend([
|
||||
{'label': _('File mimetype'), 'field': lambda x: x.file_mimetype or _('None')},
|
||||
{'label': _('File encoding'), 'field': lambda x: x.file_mime_encoding or _('None')},
|
||||
{'label': _('File size'), 'field': lambda x: pretty_size(x.size) if x.size else '-'},
|
||||
{
|
||||
'label': _('File mimetype'),
|
||||
'field': lambda x: x.file_mimetype or _('None')
|
||||
},
|
||||
{
|
||||
'label': _('File encoding'),
|
||||
'field': lambda x: x.file_mime_encoding or _('None')
|
||||
},
|
||||
{
|
||||
'label': _('File size'),
|
||||
'field': lambda x: pretty_size(x.size) if x.size else '-'
|
||||
},
|
||||
{'label': _('Exists in storage'), 'field': 'exists'},
|
||||
{'label': _('File path in storage'), 'field': 'file'},
|
||||
{'label': _('Checksum'), 'field': 'checksum'},
|
||||
{'label': _('Pages'), 'field': 'page_count'},
|
||||
])
|
||||
|
||||
document_properties_form = DocumentPropertiesForm(instance=document, extra_fields=document_fields)
|
||||
document_properties_form = DocumentPropertiesForm(
|
||||
instance=document, extra_fields=document_fields
|
||||
)
|
||||
|
||||
return render_to_response('appearance/generic_form.html', {
|
||||
'form': document_properties_form,
|
||||
@@ -268,7 +306,9 @@ def document_preview(request, document_id):
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_document_view])
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_document_view, request.user, document)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_document_view, request.user, document
|
||||
)
|
||||
|
||||
document.add_as_recent_document_for_user(request.user)
|
||||
|
||||
@@ -291,15 +331,23 @@ def document_trash(request, document_id=None, document_id_list=None):
|
||||
documents = [get_object_or_404(Document, pk=document_id)]
|
||||
post_action_redirect = reverse('documents:document_list_recent')
|
||||
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:
|
||||
messages.error(request, _('Must provide at least one document.'))
|
||||
return HttpResponseRedirect(request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL)))
|
||||
return HttpResponseRedirect(
|
||||
request.META.get(
|
||||
'HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL)
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_document_trash])
|
||||
except PermissionDenied:
|
||||
documents = AccessControlList.objects.filter_by_access(permission_document_trash, request.user, documents)
|
||||
documents = AccessControlList.objects.filter_by_access(
|
||||
permission_document_trash, request.user, documents
|
||||
)
|
||||
|
||||
previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))))
|
||||
next = request.POST.get('next', request.GET.get('next', post_action_redirect if post_action_redirect else request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))))
|
||||
|
||||
@@ -21,8 +21,14 @@ class DocumentPageImageWidget(forms.widgets.Widget):
|
||||
rotation = final_attrs.get('rotation', 0)
|
||||
if value:
|
||||
output = []
|
||||
output.append('<div class="full-height scrollable mayan-page-wrapper-interactive" data-height-difference=230>')
|
||||
output.append(document_html_widget(value, zoom=zoom, rotation=rotation, image_class='lazy-load', nolazyload=False, size=setting_display_size.value))
|
||||
output.append(
|
||||
'<div class="full-height scrollable '
|
||||
'mayan-page-wrapper-interactive" data-height-difference=230>'
|
||||
)
|
||||
output.append(document_html_widget(
|
||||
value, zoom=zoom, rotation=rotation, image_class='lazy-load',
|
||||
nolazyload=False, size=setting_display_size.value)
|
||||
)
|
||||
output.append('</div>')
|
||||
return mark_safe(''.join(output))
|
||||
else:
|
||||
@@ -35,7 +41,10 @@ class DocumentPagesCarouselWidget(forms.widgets.Widget):
|
||||
"""
|
||||
def render(self, name, value, attrs=None):
|
||||
output = []
|
||||
output.append('<div id="carousel-container" class="full-height scrollable" data-height-difference=360>')
|
||||
output.append(
|
||||
'<div id="carousel-container" class="full-height scrollable" '
|
||||
'data-height-difference=360>'
|
||||
)
|
||||
|
||||
try:
|
||||
document_pages = value.pages.all()
|
||||
@@ -57,7 +66,14 @@ class DocumentPagesCarouselWidget(forms.widgets.Widget):
|
||||
post_load_class='lazy-load-carousel-loaded',
|
||||
)
|
||||
)
|
||||
output.append('<div class="carousel-item-page-number">%s</div>' % ugettext('Page %(page_number)d of %(total_pages)d') % {'page_number': page.page_number, 'total_pages': total_pages})
|
||||
output.append(
|
||||
'<div class="carousel-item-page-number">%s</div>' % ugettext(
|
||||
'Page %(page_number)d of %(total_pages)d'
|
||||
) % {
|
||||
'page_number': page.page_number,
|
||||
'total_pages': total_pages
|
||||
}
|
||||
)
|
||||
output.append('</div>')
|
||||
|
||||
output.append('</div>')
|
||||
@@ -66,11 +82,16 @@ class DocumentPagesCarouselWidget(forms.widgets.Widget):
|
||||
|
||||
|
||||
def document_thumbnail(document, **kwargs):
|
||||
return document_html_widget(document.latest_version.pages.first(), click_view='documents:document_display', **kwargs)
|
||||
return document_html_widget(
|
||||
document.latest_version.pages.first(),
|
||||
click_view='documents:document_display', **kwargs
|
||||
)
|
||||
|
||||
|
||||
def document_link(document):
|
||||
return mark_safe('<a href="%s">%s</a>' % (document.get_absolute_url(), document))
|
||||
return mark_safe('<a href="%s">%s</a>' % (
|
||||
document.get_absolute_url(), document)
|
||||
)
|
||||
|
||||
|
||||
def document_html_widget(document_page, click_view=None, click_view_arguments=None, zoom=DEFAULT_ZOOM_LEVEL, rotation=DEFAULT_ROTATION, gallery_name=None, fancybox_class='fancybox', image_class='lazy-load', title=None, size=setting_thumbnail_size.value, nolazyload=False, post_load_class=None):
|
||||
@@ -95,9 +116,15 @@ def document_html_widget(document_page, click_view=None, click_view_arguments=No
|
||||
|
||||
query_string = urlencode(query_dict)
|
||||
|
||||
preview_view = '%s?%s' % (reverse('document-image', args=[document.pk]), query_string)
|
||||
preview_view = '%s?%s' % (
|
||||
reverse('document-image', args=[document.pk]), query_string
|
||||
)
|
||||
|
||||
result.append('<div class="tc" id="document-%d-%d">' % (document.pk, page if page else 1))
|
||||
result.append(
|
||||
'<div class="tc" id="document-%d-%d">' % (
|
||||
document.pk, page if page else 1
|
||||
)
|
||||
)
|
||||
|
||||
if title:
|
||||
title_template = 'title="%s"' % strip_tags(title)
|
||||
@@ -105,17 +132,34 @@ def document_html_widget(document_page, click_view=None, click_view_arguments=No
|
||||
title_template = ''
|
||||
|
||||
if click_view:
|
||||
result.append('<a {gallery_template} class="{fancybox_class}" href="{image_data}" {title_template}>'.format(
|
||||
gallery_template=gallery_template,
|
||||
fancybox_class=fancybox_class,
|
||||
image_data='%s?%s' % (reverse(click_view, args=click_view_arguments or [document.pk]), query_string),
|
||||
title_template=title_template
|
||||
))
|
||||
result.append(
|
||||
'<a {gallery_template} class="{fancybox_class}" '
|
||||
'href="{image_data}" {title_template}>'.format(
|
||||
gallery_template=gallery_template,
|
||||
fancybox_class=fancybox_class,
|
||||
image_data='%s?%s' % (
|
||||
reverse(
|
||||
click_view, args=click_view_arguments or [document.pk]
|
||||
), query_string
|
||||
),
|
||||
title_template=title_template
|
||||
)
|
||||
)
|
||||
|
||||
if nolazyload:
|
||||
result.append('<img class="img-nolazyload" src="%s" alt="%s" />' % (preview_view, alt_text))
|
||||
result.append(
|
||||
'<img class="img-nolazyload" src="%s" alt="%s" />' % (
|
||||
preview_view, alt_text
|
||||
)
|
||||
)
|
||||
else:
|
||||
result.append('<img class="thin_border %s" data-src="%s" data-post-load-class="%s" src="%s" alt="%s" />' % (image_class, preview_view, post_load_class, static('appearance/images/loading.png'), alt_text))
|
||||
result.append(
|
||||
'<img class="thin_border %s" data-src="%s" '
|
||||
'data-post-load-class="%s" src="%s" alt="%s" />' % (
|
||||
image_class, preview_view, post_load_class,
|
||||
static('appearance/images/loading.png'), alt_text
|
||||
)
|
||||
)
|
||||
|
||||
if click_view:
|
||||
result.append('</a>')
|
||||
|
||||
@@ -19,5 +19,12 @@ class DynamicSearchApp(MayanAppConfig):
|
||||
|
||||
APIEndPoint('search', app_name='dynamic_search')
|
||||
|
||||
menu_facet.bind_links(links=[link_search, link_search_advanced], sources=['search:search', 'search:search_advanced', 'search:results'])
|
||||
menu_sidebar.bind_links(links=[link_search_again], sources=['search:results'])
|
||||
menu_facet.bind_links(
|
||||
links=[link_search, link_search_advanced],
|
||||
sources=[
|
||||
'search:search', 'search:search_advanced', 'search:results'
|
||||
]
|
||||
)
|
||||
menu_sidebar.bind_links(
|
||||
links=[link_search_again], sources=['search:results']
|
||||
)
|
||||
|
||||
@@ -72,13 +72,15 @@ class SearchModel(object):
|
||||
findterms=re.compile(r'"([^"]+)"|(\S+)').findall,
|
||||
normspace=re.compile(r'\s{2,}').sub):
|
||||
"""
|
||||
Splits the query string in invidual keywords, getting rid of unecessary spaces
|
||||
and grouping quoted words together.
|
||||
Splits the query string in invidual keywords, getting rid of
|
||||
unecessary spaces and grouping quoted words together.
|
||||
Example:
|
||||
>>> normalize_query(' some random words "with quotes " and spaces')
|
||||
['some', 'random', 'words', 'with quotes', 'and', 'spaces']
|
||||
"""
|
||||
return [normspace(' ', (t[0] or t[1]).strip()) for t in findterms(query_string)]
|
||||
return [
|
||||
normspace(' ', (t[0] or t[1]).strip()) for t in findterms(query_string)
|
||||
]
|
||||
|
||||
def search(self, query_string, user, global_and_search=False):
|
||||
elapsed_time = 0
|
||||
@@ -102,7 +104,9 @@ class SearchModel(object):
|
||||
search_dict[search_field.get_model()]['searches'].append(
|
||||
{
|
||||
'field_name': [search_field.field],
|
||||
'terms': self.normalize_query(query_string.get('q', '').strip())
|
||||
'terms': self.normalize_query(
|
||||
query_string.get('q', '').strip()
|
||||
)
|
||||
}
|
||||
)
|
||||
else:
|
||||
@@ -117,7 +121,9 @@ class SearchModel(object):
|
||||
search_dict[search_field.get_model()]['searches'].append(
|
||||
{
|
||||
'field_name': [search_field.field],
|
||||
'terms': self.normalize_query(query_string[search_field.field])
|
||||
'terms': self.normalize_query(
|
||||
query_string[search_field.field]
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -129,7 +135,9 @@ class SearchModel(object):
|
||||
|
||||
for query_entry in data['searches']:
|
||||
# Fashion a list of queries for a field for each term
|
||||
field_query_list = self.assemble_query(query_entry['terms'], query_entry['field_name'])
|
||||
field_query_list = self.assemble_query(
|
||||
query_entry['terms'], query_entry['field_name']
|
||||
)
|
||||
|
||||
logger.debug('field_query_list: %s', field_query_list)
|
||||
|
||||
@@ -139,20 +147,26 @@ class SearchModel(object):
|
||||
# Get results per search field
|
||||
for query in field_query_list:
|
||||
logger.debug('query: %s', query)
|
||||
term_query_result_set = set(model.objects.filter(query).values_list(data['return_value'], flat=True))
|
||||
term_query_result_set = set(
|
||||
model.objects.filter(query).values_list(
|
||||
data['return_value'], flat=True
|
||||
)
|
||||
)
|
||||
|
||||
# Convert the QuerySet to a Python set and perform the
|
||||
# AND operation on the program and not as a query.
|
||||
# This operation ANDs all the field term results
|
||||
# belonging to a single model, making sure to only include
|
||||
# results in the final field result variable if all the terms
|
||||
# are found in a single field.
|
||||
# results in the final field result variable if all the
|
||||
# terms are found in a single field.
|
||||
if not field_result_set:
|
||||
field_result_set = term_query_result_set
|
||||
else:
|
||||
field_result_set &= term_query_result_set
|
||||
|
||||
logger.debug('term_query_result_set: %s', term_query_result_set)
|
||||
logger.debug(
|
||||
'term_query_result_set: %s', term_query_result_set
|
||||
)
|
||||
logger.debug('field_result_set: %s', field_result_set)
|
||||
|
||||
if global_and_search:
|
||||
@@ -165,24 +179,33 @@ class SearchModel(object):
|
||||
|
||||
result_set = result_set | model_result_set
|
||||
|
||||
elapsed_time = unicode(datetime.datetime.now() - start_time).split(':')[2]
|
||||
elapsed_time = unicode(
|
||||
datetime.datetime.now() - start_time
|
||||
).split(':')[2]
|
||||
|
||||
queryset = self.model.objects.filter(pk__in=list(result_set)[:setting_limit.value])
|
||||
queryset = self.model.objects.filter(
|
||||
pk__in=list(result_set)[:setting_limit.value]
|
||||
)
|
||||
|
||||
if self.permission:
|
||||
try:
|
||||
Permission.check_permissions(user, [self.permission])
|
||||
except PermissionDenied:
|
||||
queryset = AccessControlList.objects.filter_by_access(self.permission, user, queryset)
|
||||
queryset = AccessControlList.objects.filter_by_access(
|
||||
self.permission, user, queryset
|
||||
)
|
||||
|
||||
RecentSearch.objects.add_query_for_user(user, query_string, len(result_set))
|
||||
RecentSearch.objects.add_query_for_user(
|
||||
user, query_string, len(result_set)
|
||||
)
|
||||
|
||||
return queryset, result_set, elapsed_time
|
||||
|
||||
def assemble_query(self, terms, search_fields):
|
||||
"""
|
||||
Returns a query, that is a combination of Q objects. That combination
|
||||
aims to search keywords within a model by testing the given search fields.
|
||||
aims to search keywords within a model by testing the given search
|
||||
fields.
|
||||
"""
|
||||
queries = []
|
||||
for term in terms:
|
||||
|
||||
@@ -5,5 +5,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from navigation import Link
|
||||
|
||||
link_search = Link(text=_('Search'), view='search:search')
|
||||
link_search_advanced = Link(text=_('Advanced search'), view='search:search_advanced')
|
||||
link_search_advanced = Link(
|
||||
text=_('Advanced search'), view='search:search_advanced'
|
||||
)
|
||||
link_search_again = Link(text=_('Search again'), view='search:search_again')
|
||||
|
||||
@@ -9,7 +9,9 @@ from .settings import setting_recent_count
|
||||
|
||||
class RecentSearchManager(models.Manager):
|
||||
def add_query_for_user(self, user, query_string, hits):
|
||||
parsed_query = urlparse.parse_qs(urlencode(dict(query_string.items())))
|
||||
parsed_query = urlparse.parse_qs(
|
||||
urlencode(dict(query_string.items()))
|
||||
)
|
||||
|
||||
for key, value in parsed_query.items():
|
||||
parsed_query[key] = ' '.join(value)
|
||||
@@ -26,7 +28,10 @@ class RecentSearchManager(models.Manager):
|
||||
|
||||
if parsed_query and not isinstance(user, AnonymousUser):
|
||||
# If the URL query has at least one variable with a value
|
||||
new_recent, created = self.model.objects.get_or_create(user=user, query=urlencode(parsed_query), defaults={'hits': hits})
|
||||
new_recent, created = self.model.objects.get_or_create(
|
||||
user=user, query=urlencode(parsed_query),
|
||||
defaults={'hits': hits}
|
||||
)
|
||||
if not created:
|
||||
new_recent.hits = hits
|
||||
new_recent.save()
|
||||
|
||||
@@ -6,7 +6,9 @@ import urlparse
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
from django.utils.encoding import python_2_unicode_compatible, smart_str, smart_unicode
|
||||
from django.utils.encoding import (
|
||||
python_2_unicode_compatible, smart_str, smart_unicode
|
||||
)
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .managers import RecentSearchManager
|
||||
@@ -24,13 +26,16 @@ class RecentSearch(models.Model):
|
||||
# TODO: Fix after upgrade to DRF v2.4.4
|
||||
|
||||
query = models.TextField(editable=False, verbose_name=_('Query'))
|
||||
datetime_created = models.DateTimeField(auto_now=True, db_index=True, verbose_name=_('Datetime created'))
|
||||
datetime_created = models.DateTimeField(
|
||||
auto_now=True, db_index=True, verbose_name=_('Datetime created')
|
||||
)
|
||||
hits = models.IntegerField(editable=False, verbose_name=_('Hits'))
|
||||
|
||||
objects = RecentSearchManager()
|
||||
|
||||
def __str__(self):
|
||||
# TODO: Fix this hack, store the search model name in the recent search entry
|
||||
# TODO: Fix this hack, store the search model name in the recent
|
||||
# search entry
|
||||
from .classes import SearchModel
|
||||
document_search = SearchModel.get('documents.Document')
|
||||
|
||||
@@ -41,7 +46,11 @@ class RecentSearch(models.Model):
|
||||
advanced_string = []
|
||||
for key, value in query_dict.items():
|
||||
search_field = document_search.get_search_field(key)
|
||||
advanced_string.append('%s: %s' % (search_field.label, smart_unicode(' '.join(value))))
|
||||
advanced_string.append(
|
||||
'%s: %s' % (
|
||||
search_field.label, smart_unicode(' '.join(value))
|
||||
)
|
||||
)
|
||||
|
||||
display_string = ', '.join(advanced_string)
|
||||
else:
|
||||
|
||||
@@ -6,6 +6,14 @@ from smart_settings import Namespace
|
||||
|
||||
|
||||
namespace = Namespace(name='dynamic_search', label=_('Search'))
|
||||
setting_show_object_type = namespace.add_setting(global_name='SEARCH_SHOW_OBJECT_TYPE', default=False)
|
||||
setting_limit = namespace.add_setting(global_name='SEARCH_LIMIT', default=100, help_text=_('Maximum amount search hits to fetch and display.'))
|
||||
setting_recent_count = namespace.add_setting(global_name='SEARCH_RECENT_COUNT', default=5, help_text=_('Maximum number of search queries to remember per user.'))
|
||||
setting_show_object_type = namespace.add_setting(
|
||||
global_name='SEARCH_SHOW_OBJECT_TYPE', default=False
|
||||
)
|
||||
setting_limit = namespace.add_setting(
|
||||
global_name='SEARCH_LIMIT', default=100,
|
||||
help_text=_('Maximum amount search hits to fetch and display.')
|
||||
)
|
||||
setting_recent_count = namespace.add_setting(
|
||||
global_name='SEARCH_RECENT_COUNT', default=5,
|
||||
help_text=_('Maximum number of search queries to remember per user.')
|
||||
)
|
||||
|
||||
@@ -14,11 +14,18 @@ from documents.test_models import (
|
||||
|
||||
class DocumentSearchTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.admin_user = User.objects.create_superuser(username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL, password=TEST_ADMIN_PASSWORD)
|
||||
self.document_type = DocumentType.objects.create(label=TEST_DOCUMENT_TYPE)
|
||||
self.admin_user = User.objects.create_superuser(
|
||||
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
|
||||
password=TEST_ADMIN_PASSWORD
|
||||
)
|
||||
self.document_type = DocumentType.objects.create(
|
||||
label=TEST_DOCUMENT_TYPE
|
||||
)
|
||||
|
||||
with open(TEST_SMALL_DOCUMENT_PATH) as file_object:
|
||||
self.document = self.document_type.new_document(file_object=File(file_object), label='mayan_11_1.pdf')
|
||||
self.document = self.document_type.new_document(
|
||||
file_object=File(file_object), label='mayan_11_1.pdf'
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
self.document.delete()
|
||||
@@ -30,17 +37,24 @@ class DocumentSearchTestCase(TestCase):
|
||||
document versions and document version pages
|
||||
"""
|
||||
|
||||
model_list, result_set, elapsed_time = document_search.search({'q': 'Mayan'}, user=self.admin_user)
|
||||
model_list, result_set, elapsed_time = document_search.search(
|
||||
{'q': 'Mayan'}, user=self.admin_user
|
||||
)
|
||||
self.assertEqual(len(result_set), 1)
|
||||
self.assertEqual(list(model_list), [self.document])
|
||||
|
||||
def test_advanced_search_after_related_name_change(self):
|
||||
# Test versions__filename
|
||||
model_list, result_set, elapsed_time = document_search.search({'label': self.document.label}, user=self.admin_user)
|
||||
model_list, result_set, elapsed_time = document_search.search(
|
||||
{'label': self.document.label}, user=self.admin_user
|
||||
)
|
||||
self.assertEqual(len(result_set), 1)
|
||||
self.assertEqual(list(model_list), [self.document])
|
||||
|
||||
# Test versions__mimetype
|
||||
model_list, result_set, elapsed_time = document_search.search({'versions__mimetype': self.document.file_mimetype}, user=self.admin_user)
|
||||
model_list, result_set, elapsed_time = document_search.search(
|
||||
{'versions__mimetype': self.document.file_mimetype},
|
||||
user=self.admin_user
|
||||
)
|
||||
self.assertEqual(len(result_set), 1)
|
||||
self.assertEqual(list(model_list), [self.document])
|
||||
|
||||
@@ -22,16 +22,23 @@ class Issue46TestCase(TestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.admin_user = User.objects.create_superuser(username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL, password=TEST_ADMIN_PASSWORD)
|
||||
self.admin_user = User.objects.create_superuser(
|
||||
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
|
||||
password=TEST_ADMIN_PASSWORD
|
||||
)
|
||||
self.client = Client()
|
||||
# Login the admin user
|
||||
logged_in = self.client.login(username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD)
|
||||
logged_in = self.client.login(
|
||||
username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD
|
||||
)
|
||||
self.assertTrue(logged_in)
|
||||
self.assertTrue(self.admin_user.is_authenticated())
|
||||
|
||||
self.document_count = 4
|
||||
|
||||
self.document_type = DocumentType.objects.create(label=TEST_DOCUMENT_TYPE)
|
||||
self.document_type = DocumentType.objects.create(
|
||||
label=TEST_DOCUMENT_TYPE
|
||||
)
|
||||
|
||||
# Upload many instances of the same test document
|
||||
for i in range(self.document_count):
|
||||
@@ -49,16 +56,28 @@ class Issue46TestCase(TestCase):
|
||||
|
||||
def test_advanced_search_past_first_page(self):
|
||||
# Make sure all documents are returned by the search
|
||||
model_list, result_set, elapsed_time = document_search.search({'label': 'test document'}, user=self.admin_user)
|
||||
model_list, result_set, elapsed_time = document_search.search(
|
||||
{'label': 'test document'}, user=self.admin_user
|
||||
)
|
||||
self.assertEqual(len(result_set), self.document_count)
|
||||
|
||||
with self.settings(PAGINATION_DEFAULT_PAGINATION=2):
|
||||
reload(pagination_tags)
|
||||
|
||||
# Funcitonal test for the first page of advanced results
|
||||
response = self.client.get(reverse('search:results'), {'label': 'test'})
|
||||
self.assertContains(response, 'Total (1 - 2 out of 4) (Page 1 of 2)', status_code=200)
|
||||
response = self.client.get(
|
||||
reverse('search:results'), {'label': 'test'}
|
||||
)
|
||||
self.assertContains(
|
||||
response, 'Total (1 - 2 out of 4) (Page 1 of 2)',
|
||||
status_code=200
|
||||
)
|
||||
|
||||
# Functional test for the second page of advanced results
|
||||
response = self.client.get(reverse('search:results'), {'label': 'test', 'page': 2})
|
||||
self.assertContains(response, 'Total (3 - 4 out of 4) (Page 2 of 2)', status_code=200)
|
||||
response = self.client.get(
|
||||
reverse('search:results'), {'label': 'test', 'page': 2}
|
||||
)
|
||||
self.assertContains(
|
||||
response, 'Total (3 - 4 out of 4) (Page 2 of 2)',
|
||||
status_code=200
|
||||
)
|
||||
|
||||
@@ -16,7 +16,13 @@ urlpatterns = patterns(
|
||||
|
||||
api_urls = patterns(
|
||||
'',
|
||||
url(r'^recent_searches/$', APIRecentSearchListView.as_view(), name='recentsearch-list'),
|
||||
url(r'^recent_searches/(?P<pk>[0-9]+)/$', APIRecentSearchView.as_view(), name='recentsearch-detail'),
|
||||
url(
|
||||
r'^recent_searches/$', APIRecentSearchListView.as_view(),
|
||||
name='recentsearch-list'
|
||||
),
|
||||
url(
|
||||
r'^recent_searches/(?P<pk>[0-9]+)/$', APIRecentSearchView.as_view(),
|
||||
name='recentsearch-detail'
|
||||
),
|
||||
url(r'^search/$', APISearchView.as_view(), name='search-view'),
|
||||
)
|
||||
|
||||
@@ -44,18 +44,27 @@ def results(request, extra_context=None):
|
||||
|
||||
if setting_show_object_type.value:
|
||||
context.update({
|
||||
'extra_columns': [{'name': _('Type'), 'attribute': lambda x: x._meta.verbose_name[0].upper() + x._meta.verbose_name[1:]}]
|
||||
'extra_columns': [
|
||||
{
|
||||
'name': _('Type'),
|
||||
'attribute': lambda x: x._meta.verbose_name[0].upper() + x._meta.verbose_name[1:]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
return render_to_response('dynamic_search/search_results.html', context,
|
||||
context_instance=RequestContext(request))
|
||||
return render_to_response(
|
||||
'dynamic_search/search_results.html', context,
|
||||
context_instance=RequestContext(request)
|
||||
)
|
||||
|
||||
|
||||
def search(request, advanced=False):
|
||||
document_search = SearchModel.get('documents.Document')
|
||||
|
||||
if advanced:
|
||||
form = AdvancedSearchForm(data=request.GET, search_model=document_search)
|
||||
form = AdvancedSearchForm(
|
||||
data=request.GET, search_model=document_search
|
||||
)
|
||||
return render_to_response(
|
||||
'appearance/generic_form.html',
|
||||
{
|
||||
@@ -87,5 +96,9 @@ def search(request, advanced=False):
|
||||
|
||||
|
||||
def search_again(request):
|
||||
query = urlparse.urlparse(request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))).query
|
||||
return HttpResponseRedirect('%s?%s' % (reverse('search:search_advanced'), query))
|
||||
query = urlparse.urlparse(
|
||||
request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))
|
||||
).query
|
||||
return HttpResponseRedirect(
|
||||
'%s?%s' % (reverse('search:search_advanced'), query)
|
||||
)
|
||||
|
||||
@@ -21,6 +21,8 @@ class EventsApp(MayanAppConfig):
|
||||
|
||||
SourceColumn(source=Action, label=_('Timestamp'), attribute='timestamp')
|
||||
SourceColumn(source=Action, label=_('Actor'), attribute='actor')
|
||||
SourceColumn(source=Action, label=_('Verb'), attribute=encapsulate(lambda entry: event_type_link(entry)))
|
||||
SourceColumn(source=Action, label=_('Verb'), attribute=encapsulate(
|
||||
lambda entry: event_type_link(entry))
|
||||
)
|
||||
|
||||
menu_tools.bind_links(links=[link_events_list])
|
||||
|
||||
@@ -28,4 +28,7 @@ class Event(object):
|
||||
if not self.event_type:
|
||||
self.event_type, created = model.objects.get_or_create(name=self.name)
|
||||
|
||||
action.send(actor or target, actor=actor, verb=self.name, action_object=action_object, target=target)
|
||||
action.send(
|
||||
actor or target, actor=actor, verb=self.name,
|
||||
action_object=action_object, target=target
|
||||
)
|
||||
|
||||
@@ -11,10 +11,21 @@ from .permissions import permission_events_view
|
||||
def get_kwargs_factory(variable_name):
|
||||
def get_kwargs(context):
|
||||
content_type = ContentType.objects.get_for_model(context[variable_name])
|
||||
return {'app_label': '"{}"'.format(content_type.app_label), 'model': '"{}"'.format(content_type.model), 'object_id': '{}.pk'.format(variable_name)}
|
||||
return {
|
||||
'app_label': '"{}"'.format(content_type.app_label),
|
||||
'model': '"{}"'.format(content_type.model),
|
||||
'object_id': '{}.pk'.format(variable_name)
|
||||
}
|
||||
|
||||
return get_kwargs
|
||||
|
||||
|
||||
link_events_list = Link(icon='fa fa-list-ol', permissions=[permission_events_view], text=_('Events'), view='events:events_list')
|
||||
link_events_for_object = Link(permissions=[permission_events_view], text=_('Events'), view='events:events_for_object', kwargs=get_kwargs_factory('resolved_object'))
|
||||
link_events_list = Link(
|
||||
icon='fa fa-list-ol', permissions=[permission_events_view],
|
||||
text=_('Events'), view='events:events_list'
|
||||
)
|
||||
link_events_for_object = Link(
|
||||
permissions=[permission_events_view], text=_('Events'),
|
||||
view='events:events_for_object',
|
||||
kwargs=get_kwargs_factory('resolved_object')
|
||||
)
|
||||
|
||||
@@ -9,7 +9,9 @@ from .classes import Event
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class EventType(models.Model):
|
||||
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
|
||||
name = models.CharField(
|
||||
max_length=64, unique=True, verbose_name=_('Name')
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return unicode(Event.get_label(self.name))
|
||||
|
||||
@@ -5,4 +5,6 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from permissions import PermissionNamespace
|
||||
|
||||
namespace = PermissionNamespace('events', _('Events'))
|
||||
permission_events_view = namespace.add_permission(name='events_view', label=_('Access the events of an object'))
|
||||
permission_events_view = namespace.add_permission(
|
||||
name='events_view', label=_('Access the events of an object')
|
||||
)
|
||||
|
||||
@@ -7,6 +7,12 @@ from .views import EventListView, ObjectEventListView, VerbEventListView
|
||||
urlpatterns = patterns(
|
||||
'events.views',
|
||||
url(r'^all/$', EventListView.as_view(), name='events_list'),
|
||||
url(r'^for/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/$', ObjectEventListView.as_view(), name='events_for_object'),
|
||||
url(r'^by_verb/(?P<verb>[\w\-]+)/$', VerbEventListView.as_view(), name='events_by_verb'),
|
||||
url(
|
||||
r'^for/(?P<app_label>[-\w]+)/(?P<model>[-\w]+)/(?P<object_id>\d+)/$',
|
||||
ObjectEventListView.as_view(), name='events_for_object'
|
||||
),
|
||||
url(
|
||||
r'^by_verb/(?P<verb>[\w\-]+)/$', VerbEventListView.as_view(),
|
||||
name='events_by_verb'
|
||||
),
|
||||
)
|
||||
|
||||
@@ -29,7 +29,9 @@ class EventListView(SingleObjectListView):
|
||||
'extra_columns': [
|
||||
{
|
||||
'name': _('Target'),
|
||||
'attribute': encapsulate(lambda entry: event_object_link(entry))
|
||||
'attribute': encapsulate(
|
||||
lambda entry: event_object_link(entry)
|
||||
)
|
||||
}
|
||||
],
|
||||
'hide_object': True,
|
||||
@@ -41,19 +43,30 @@ class ObjectEventListView(EventListView):
|
||||
view_permissions = None
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.content_type = get_object_or_404(ContentType, app_label=self.kwargs['app_label'], model=self.kwargs['model'])
|
||||
self.content_type = get_object_or_404(
|
||||
ContentType, app_label=self.kwargs['app_label'],
|
||||
model=self.kwargs['model']
|
||||
)
|
||||
|
||||
try:
|
||||
self.content_object = self.content_type.get_object_for_this_type(pk=self.kwargs['object_id'])
|
||||
self.content_object = self.content_type.get_object_for_this_type(
|
||||
pk=self.kwargs['object_id']
|
||||
)
|
||||
except self.content_type.model_class().DoesNotExist:
|
||||
raise Http404
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, permissions=(permission_events_view,))
|
||||
Permission.check_permissions(
|
||||
request.user, permissions=(permission_events_view,)
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_events_view, request.user, self.content_object)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_events_view, request.user, self.content_object
|
||||
)
|
||||
|
||||
return super(ObjectEventListView, self).dispatch(request, *args, **kwargs)
|
||||
return super(
|
||||
ObjectEventListView, self
|
||||
).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_queryset(self):
|
||||
return any_stream(self.content_object)
|
||||
@@ -75,9 +88,13 @@ class VerbEventListView(SingleObjectListView):
|
||||
'extra_columns': [
|
||||
{
|
||||
'name': _('Target'),
|
||||
'attribute': encapsulate(lambda entry: event_object_link(entry))
|
||||
'attribute': encapsulate(
|
||||
lambda entry: event_object_link(entry)
|
||||
)
|
||||
}
|
||||
],
|
||||
'hide_object': True,
|
||||
'title': _('Events of type: %s') % Event.get_label(self.kwargs['verb']),
|
||||
'title': _(
|
||||
'Events of type: %s'
|
||||
) % Event.get_label(self.kwargs['verb']),
|
||||
}
|
||||
|
||||
@@ -32,15 +32,21 @@ class APIFolderListView(generics.ListCreateAPIView):
|
||||
mayan_view_permissions = {'POST': [permission_folder_create]}
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
"""Returns a list of all the folders."""
|
||||
"""
|
||||
Returns a list of all the folders.
|
||||
"""
|
||||
return super(APIFolderListView, self).get(*args, **kwargs)
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
"""Create a new folder."""
|
||||
"""
|
||||
Create a new folder.
|
||||
"""
|
||||
return super(APIFolderListView, self).post(*args, **kwargs)
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
|
||||
serializer = self.get_serializer(
|
||||
data=request.DATA, files=request.FILES
|
||||
)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.object.user = request.user
|
||||
@@ -48,8 +54,10 @@ class APIFolderListView(generics.ListCreateAPIView):
|
||||
self.object = serializer.save(force_insert=True)
|
||||
self.post_save(self.object, created=True)
|
||||
headers = self.get_success_headers(serializer.data)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED,
|
||||
headers=headers)
|
||||
return Response(
|
||||
serializer.data, status=status.HTTP_201_CREATED,
|
||||
headers=headers
|
||||
)
|
||||
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@@ -67,24 +75,34 @@ class APIFolderView(generics.RetrieveUpdateDestroyAPIView):
|
||||
}
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
"""Delete the selected folder."""
|
||||
"""
|
||||
Delete the selected folder.
|
||||
"""
|
||||
return super(APIFolderView, self).delete(*args, **kwargs)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
"""Returns the details of the selected folder."""
|
||||
"""
|
||||
Returns the details of the selected folder.
|
||||
"""
|
||||
return super(APIFolderView, self).get(*args, **kwargs)
|
||||
|
||||
def patch(self, *args, **kwargs):
|
||||
"""Edit the selected folder."""
|
||||
"""
|
||||
Edit the selected folder.
|
||||
"""
|
||||
return super(APIFolderView, self).patch(*args, **kwargs)
|
||||
|
||||
def put(self, *args, **kwargs):
|
||||
"""Edit the selected folder."""
|
||||
"""
|
||||
Edit the selected folder.
|
||||
"""
|
||||
return super(APIFolderView, self).put(*args, **kwargs)
|
||||
|
||||
|
||||
class APIFolderDocumentListView(generics.ListAPIView):
|
||||
"""Returns a list of all the documents contained in a particular folder."""
|
||||
"""
|
||||
Returns a list of all the documents contained in a particular folder.
|
||||
"""
|
||||
|
||||
filter_backends = (MayanObjectPermissionsFilter,)
|
||||
mayan_object_permissions = {'GET': [permission_document_view]}
|
||||
@@ -96,15 +114,21 @@ class APIFolderDocumentListView(generics.ListAPIView):
|
||||
def get_queryset(self):
|
||||
folder = get_object_or_404(Folder, pk=self.kwargs['pk'])
|
||||
try:
|
||||
Permission.check_permissions(self.request.user, [permission_folder_view])
|
||||
Permission.check_permissions(
|
||||
self.request.user, [permission_folder_view]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_folder_view, self.request.user, folder)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_folder_view, self.request.user, folder
|
||||
)
|
||||
|
||||
return folder.documents.all()
|
||||
|
||||
|
||||
class APIDocumentFolderListView(generics.ListAPIView):
|
||||
"""Returns a list of all the folders to which a document belongs."""
|
||||
"""
|
||||
Returns a list of all the folders to which a document belongs.
|
||||
"""
|
||||
|
||||
serializer_class = FolderSerializer
|
||||
|
||||
@@ -114,9 +138,13 @@ class APIDocumentFolderListView(generics.ListAPIView):
|
||||
def get_queryset(self):
|
||||
document = get_object_or_404(Document, pk=self.kwargs['pk'])
|
||||
try:
|
||||
Permission.check_permissions(self.request.user, [permission_document_view])
|
||||
Permission.check_permissions(
|
||||
self.request.user, [permission_document_view]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_document_view, self.request.user, document)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_document_view, self.request.user, document
|
||||
)
|
||||
|
||||
queryset = document.folders.all()
|
||||
return queryset
|
||||
@@ -125,13 +153,19 @@ class APIDocumentFolderListView(generics.ListAPIView):
|
||||
class APIFolderDocumentView(views.APIView):
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
"""Remove a document from the selected folder."""
|
||||
"""
|
||||
Remove a document from the selected folder.
|
||||
"""
|
||||
|
||||
folder = get_object_or_404(Folder, pk=self.kwargs['pk'])
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_folder_remove_document])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_folder_remove_document]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_folder_remove_document, request.user, folder)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_folder_remove_document, request.user, folder
|
||||
)
|
||||
|
||||
document = get_object_or_404(Document, pk=self.kwargs['document_pk'])
|
||||
folder.documents.remove(document)
|
||||
@@ -139,13 +173,19 @@ class APIFolderDocumentView(views.APIView):
|
||||
|
||||
# TODO: move this method as post of APIFolderDocumentListView
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""Add a document to the selected folder."""
|
||||
"""
|
||||
Add a document to the selected folder.
|
||||
"""
|
||||
|
||||
folder = get_object_or_404(Folder, pk=self.kwargs['pk'])
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_folder_add_document])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_folder_add_document]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_folder_add_document, request.user, folder)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_folder_add_document, request.user, folder
|
||||
)
|
||||
|
||||
document = get_object_or_404(Document, pk=self.kwargs['document_pk'])
|
||||
folder.documents.add(document)
|
||||
|
||||
@@ -39,24 +39,48 @@ class FoldersApp(MayanAppConfig):
|
||||
|
||||
ModelPermission.register(
|
||||
model=Document, permissions=(
|
||||
permission_folder_add_document, permission_folder_remove_document
|
||||
permission_folder_add_document,
|
||||
permission_folder_remove_document
|
||||
)
|
||||
)
|
||||
|
||||
ModelPermission.register(
|
||||
model=Folder, permissions=(
|
||||
permission_acl_edit, permission_acl_view, permission_folder_delete,
|
||||
permission_folder_edit, permission_folder_view
|
||||
permission_acl_edit, permission_acl_view,
|
||||
permission_folder_delete, permission_folder_edit,
|
||||
permission_folder_view
|
||||
)
|
||||
)
|
||||
|
||||
menu_facet.bind_links(links=[link_document_folder_list], sources=[Document])
|
||||
menu_facet.bind_links(
|
||||
links=[link_document_folder_list], sources=[Document]
|
||||
)
|
||||
menu_main.bind_links(links=[link_folder_list])
|
||||
menu_multi_item.bind_links(links=[link_folder_add_multiple_documents], sources=[Document])
|
||||
menu_multi_item.bind_links(links=[link_folder_document_multiple_remove], sources=[CombinedSource(obj=Document, view='folders:folder_view')])
|
||||
menu_object.bind_links(links=[link_folder_view, link_folder_edit, link_acl_list, link_folder_delete], sources=[Folder])
|
||||
menu_secondary.bind_links(links=[link_folder_list, link_folder_create], sources=[Folder, 'folders:folder_list', 'folders:folder_create'])
|
||||
menu_sidebar.bind_links(links=[link_folder_add_document], sources=['folders:document_folder_list', 'folders:folder_add_document'])
|
||||
menu_multi_item.bind_links(
|
||||
links=[link_folder_add_multiple_documents], sources=[Document]
|
||||
)
|
||||
menu_multi_item.bind_links(
|
||||
links=[link_folder_document_multiple_remove],
|
||||
sources=[CombinedSource(obj=Document, view='folders:folder_view')]
|
||||
)
|
||||
menu_object.bind_links(
|
||||
links=[
|
||||
link_folder_view, link_folder_edit, link_acl_list,
|
||||
link_folder_delete
|
||||
], sources=[Folder]
|
||||
)
|
||||
menu_secondary.bind_links(
|
||||
links=[link_folder_list, link_folder_create],
|
||||
sources=[Folder, 'folders:folder_list', 'folders:folder_create']
|
||||
)
|
||||
menu_sidebar.bind_links(
|
||||
links=[link_folder_add_document],
|
||||
sources=[
|
||||
'folders:document_folder_list', 'folders:folder_add_document'
|
||||
]
|
||||
)
|
||||
|
||||
SourceColumn(source=Folder, label=_('Created'), attribute='datetime_created')
|
||||
SourceColumn(
|
||||
source=Folder, label=_('Created'), attribute='datetime_created'
|
||||
)
|
||||
SourceColumn(source=Folder, label=_('User'), attribute='user')
|
||||
|
||||
@@ -25,7 +25,9 @@ class FolderListForm(forms.Form):
|
||||
try:
|
||||
Permission.check_permissions(user, [permission_folder_view])
|
||||
except PermissionDenied:
|
||||
queryset = AccessControlList.objects.filter_by_access(permission_folder_view, user, queryset)
|
||||
queryset = AccessControlList.objects.filter_by_access(
|
||||
permission_folder_view, user, queryset
|
||||
)
|
||||
|
||||
self.fields['folder'] = forms.ModelChoiceField(
|
||||
queryset=queryset,
|
||||
|
||||
@@ -11,12 +11,38 @@ from .permissions import (
|
||||
permission_folder_remove_document
|
||||
)
|
||||
|
||||
link_document_folder_list = Link(permissions=[permission_document_view], text=_('Folders'), view='folders:document_folder_list', args='object.pk')
|
||||
link_folder_add_document = Link(permissions=[permission_folder_add_document], text=_('Add to a folder'), view='folders:folder_add_document', args='object.pk')
|
||||
link_folder_add_multiple_documents = Link(text=_('Add to folder'), view='folders:folder_add_multiple_documents')
|
||||
link_folder_create = Link(permissions=[permission_folder_create], text=_('Create folder'), view='folders:folder_create')
|
||||
link_folder_delete = Link(permissions=[permission_folder_delete], tags='dangerous', text=_('Delete'), view='folders:folder_delete', args='object.pk')
|
||||
link_folder_document_multiple_remove = Link(permissions=[permission_folder_remove_document], text=_('Remove from folder'), view='folders:folder_document_multiple_remove', args='object.pk')
|
||||
link_folder_edit = Link(permissions=[permission_folder_edit], text=_('Edit'), view='folders:folder_edit', args='object.pk')
|
||||
link_folder_list = Link(icon='fa fa-folder', text=_('Folders'), view='folders:folder_list')
|
||||
link_folder_view = Link(permissions=[permission_folder_view], text=_('Documents'), view='folders:folder_view', args='object.pk')
|
||||
link_document_folder_list = Link(
|
||||
permissions=[permission_document_view], text=_('Folders'),
|
||||
view='folders:document_folder_list', args='object.pk'
|
||||
)
|
||||
link_folder_add_document = Link(
|
||||
permissions=[permission_folder_add_document], text=_('Add to a folder'),
|
||||
view='folders:folder_add_document', args='object.pk'
|
||||
)
|
||||
link_folder_add_multiple_documents = Link(
|
||||
text=_('Add to folder'), view='folders:folder_add_multiple_documents'
|
||||
)
|
||||
link_folder_create = Link(
|
||||
permissions=[permission_folder_create], text=_('Create folder'),
|
||||
view='folders:folder_create'
|
||||
)
|
||||
link_folder_delete = Link(
|
||||
permissions=[permission_folder_delete], tags='dangerous',
|
||||
text=_('Delete'), view='folders:folder_delete', args='object.pk'
|
||||
)
|
||||
link_folder_document_multiple_remove = Link(
|
||||
permissions=[permission_folder_remove_document],
|
||||
text=_('Remove from folder'),
|
||||
view='folders:folder_document_multiple_remove', args='object.pk'
|
||||
)
|
||||
link_folder_edit = Link(
|
||||
permissions=[permission_folder_edit], text=_('Edit'),
|
||||
view='folders:folder_edit', args='object.pk'
|
||||
)
|
||||
link_folder_list = Link(
|
||||
icon='fa fa-folder', text=_('Folders'), view='folders:folder_list'
|
||||
)
|
||||
link_folder_view = Link(
|
||||
permissions=[permission_folder_view], text=_('Documents'),
|
||||
view='folders:folder_view', args='object.pk'
|
||||
)
|
||||
|
||||
@@ -11,10 +11,16 @@ from documents.models import Document
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Folder(models.Model):
|
||||
label = models.CharField(db_index=True, max_length=128, verbose_name=_('Label'))
|
||||
label = models.CharField(
|
||||
db_index=True, max_length=128, verbose_name=_('Label')
|
||||
)
|
||||
user = models.ForeignKey(User, verbose_name=_('User'))
|
||||
datetime_created = models.DateTimeField(auto_now_add=True, verbose_name=_('Datetime created'))
|
||||
documents = models.ManyToManyField(Document, related_name='folders', verbose_name=_('Documents'))
|
||||
datetime_created = models.DateTimeField(
|
||||
auto_now_add=True, verbose_name=_('Datetime created')
|
||||
)
|
||||
documents = models.ManyToManyField(
|
||||
Document, related_name='folders', verbose_name=_('Documents')
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
|
||||
@@ -6,9 +6,21 @@ from permissions import PermissionNamespace
|
||||
|
||||
namespace = PermissionNamespace('folders', _('Folders'))
|
||||
|
||||
permission_folder_create = namespace.add_permission(name='folder_create', label=_('Create new folders'))
|
||||
permission_folder_edit = namespace.add_permission(name='folder_edit', label=_('Edit new folders'))
|
||||
permission_folder_delete = namespace.add_permission(name='folder_delete', label=_('Delete new folders'))
|
||||
permission_folder_remove_document = namespace.add_permission(name='folder_remove_document', label=_('Remove documents from folders'))
|
||||
permission_folder_view = namespace.add_permission(name='folder_view', label=_('View existing folders'))
|
||||
permission_folder_add_document = namespace.add_permission(name='folder_add_document', label=_('Add documents to existing folders'))
|
||||
permission_folder_create = namespace.add_permission(
|
||||
name='folder_create', label=_('Create new folders')
|
||||
)
|
||||
permission_folder_edit = namespace.add_permission(
|
||||
name='folder_edit', label=_('Edit new folders')
|
||||
)
|
||||
permission_folder_delete = namespace.add_permission(
|
||||
name='folder_delete', label=_('Delete new folders')
|
||||
)
|
||||
permission_folder_remove_document = namespace.add_permission(
|
||||
name='folder_remove_document', label=_('Remove documents from folders')
|
||||
)
|
||||
permission_folder_view = namespace.add_permission(
|
||||
name='folder_view', label=_('View existing folders')
|
||||
)
|
||||
permission_folder_add_document = namespace.add_permission(
|
||||
name='folder_add_document', label=_('Add documents to existing folders')
|
||||
)
|
||||
|
||||
@@ -15,17 +15,26 @@ from documents.test_models import TEST_DOCUMENT_TYPE
|
||||
|
||||
from .models import Folder
|
||||
|
||||
TEST_DOCUMENT_PATH = os.path.join(settings.BASE_DIR, 'contrib', 'sample_documents', 'title_page.png')
|
||||
TEST_DOCUMENT_PATH = os.path.join(
|
||||
settings.BASE_DIR, 'contrib', 'sample_documents', 'title_page.png'
|
||||
)
|
||||
|
||||
|
||||
class FolderTestCase(TestCase):
|
||||
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_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)
|
||||
)
|
||||
|
||||
self.user = User.objects.create_superuser(username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL, password=TEST_ADMIN_PASSWORD)
|
||||
self.user = User.objects.create_superuser(
|
||||
username=TEST_ADMIN_USERNAME, email=TEST_ADMIN_EMAIL,
|
||||
password=TEST_ADMIN_PASSWORD
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
self.document.delete()
|
||||
|
||||
@@ -16,20 +16,47 @@ urlpatterns = patterns(
|
||||
url(r'^list/$', FolderListView.as_view(), name='folder_list'),
|
||||
url(r'^create/$', FolderCreateView.as_view(), name='folder_create'),
|
||||
url(r'^(?P<pk>\d+)/edit/$', FolderEditView.as_view(), name='folder_edit'),
|
||||
url(r'^(?P<folder_id>\d+)/delete/$', 'folder_delete', name='folder_delete'),
|
||||
url(
|
||||
r'^(?P<folder_id>\d+)/delete/$', 'folder_delete', name='folder_delete'
|
||||
),
|
||||
url(r'^(?P<pk>\d+)/$', FolderDetailView.as_view(), name='folder_view'),
|
||||
url(r'^(?P<folder_id>\d+)/remove/document/multiple/$', 'folder_document_multiple_remove', name='folder_document_multiple_remove'),
|
||||
url(
|
||||
r'^(?P<folder_id>\d+)/remove/document/multiple/$',
|
||||
'folder_document_multiple_remove',
|
||||
name='folder_document_multiple_remove'
|
||||
),
|
||||
|
||||
url(r'^document/(?P<document_id>\d+)/folder/add/$', 'folder_add_document', name='folder_add_document'),
|
||||
url(r'^document/multiple/folder/add/$', 'folder_add_multiple_documents', name='folder_add_multiple_documents'),
|
||||
url(r'^document/(?P<pk>\d+)/folder/list/$', DocumentFolderListView.as_view(), name='document_folder_list'),
|
||||
url(
|
||||
r'^document/(?P<document_id>\d+)/folder/add/$',
|
||||
'folder_add_document', name='folder_add_document'
|
||||
),
|
||||
url(
|
||||
r'^document/multiple/folder/add/$', 'folder_add_multiple_documents',
|
||||
name='folder_add_multiple_documents'
|
||||
),
|
||||
url(
|
||||
r'^document/(?P<pk>\d+)/folder/list/$',
|
||||
DocumentFolderListView.as_view(), name='document_folder_list'
|
||||
),
|
||||
)
|
||||
|
||||
api_urls = patterns(
|
||||
'',
|
||||
url(r'^folders/(?P<pk>[0-9]+)/documents/(?P<document_pk>[0-9]+)/$', APIFolderDocumentView.as_view(), name='folder-document'),
|
||||
url(r'^folders/(?P<pk>[0-9]+)/documents/$', APIFolderDocumentListView.as_view(), name='folder-document-list'),
|
||||
url(r'^folders/(?P<pk>[0-9]+)/$', APIFolderView.as_view(), name='folder-detail'),
|
||||
url(
|
||||
r'^folders/(?P<pk>[0-9]+)/documents/(?P<document_pk>[0-9]+)/$',
|
||||
APIFolderDocumentView.as_view(), name='folder-document'
|
||||
),
|
||||
url(
|
||||
r'^folders/(?P<pk>[0-9]+)/documents/$',
|
||||
APIFolderDocumentListView.as_view(), name='folder-document-list'
|
||||
),
|
||||
url(
|
||||
r'^folders/(?P<pk>[0-9]+)/$', APIFolderView.as_view(),
|
||||
name='folder-detail'
|
||||
),
|
||||
url(r'^folders/$', APIFolderListView.as_view(), name='folder-list'),
|
||||
url(r'^document/(?P<pk>[0-9]+)/folders/$', APIDocumentFolderListView.as_view(), name='document-folder-list'),
|
||||
url(
|
||||
r'^document/(?P<pk>[0-9]+)/folders/$',
|
||||
APIDocumentFolderListView.as_view(), name='document-folder-list'
|
||||
),
|
||||
)
|
||||
|
||||
@@ -53,7 +53,9 @@ class FolderListView(SingleObjectListView):
|
||||
try:
|
||||
Permission.check_permissions(user, [permission_document_view])
|
||||
except PermissionDenied:
|
||||
queryset = AccessControlList.objects.filter_by_access(permission_document_view, user, queryset)
|
||||
queryset = AccessControlList.objects.filter_by_access(
|
||||
permission_document_view, user, queryset
|
||||
)
|
||||
|
||||
return queryset.count()
|
||||
|
||||
@@ -69,7 +71,14 @@ class FolderListView(SingleObjectListView):
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'extra_columns': [
|
||||
{'name': _('Documents'), 'attribute': encapsulate(lambda instance: FolderListView.get_document_count(instance=instance, user=self.request.user))},
|
||||
{
|
||||
'name': _('Documents'),
|
||||
'attribute': encapsulate(
|
||||
lambda instance: FolderListView.get_document_count(
|
||||
instance=instance, user=self.request.user
|
||||
)
|
||||
)
|
||||
},
|
||||
],
|
||||
'title': _('Folders'),
|
||||
'hide_link': True,
|
||||
@@ -83,14 +92,21 @@ class FolderCreateView(SingleObjectCreateView):
|
||||
|
||||
def form_valid(self, form):
|
||||
try:
|
||||
Folder.objects.get(label=form.cleaned_data['label'], user=self.request.user)
|
||||
Folder.objects.get(
|
||||
label=form.cleaned_data['label'], user=self.request.user
|
||||
)
|
||||
except Folder.DoesNotExist:
|
||||
instance = form.save(commit=False)
|
||||
instance.user = self.request.user
|
||||
instance.save()
|
||||
return super(FolderCreateView, self).form_valid(form)
|
||||
else:
|
||||
messages.error(self.request, _('A folder named: %s, already exists.') % form.cleaned_data['label'])
|
||||
messages.error(
|
||||
self.request,
|
||||
_(
|
||||
'A folder named: %s, already exists.'
|
||||
) % form.cleaned_data['label']
|
||||
)
|
||||
return super(FolderCreateView, self).form_invalid(form)
|
||||
|
||||
def get_extra_context(self):
|
||||
@@ -105,7 +121,9 @@ def folder_delete(request, folder_id):
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_folder_delete])
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_folder_delete, request.user, folder)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_folder_delete, request.user, folder
|
||||
)
|
||||
|
||||
post_action_redirect = reverse('folders:folder_list')
|
||||
|
||||
@@ -139,9 +157,13 @@ class FolderDetailView(DocumentListView):
|
||||
folder = get_object_or_404(Folder, pk=self.kwargs['pk'])
|
||||
|
||||
try:
|
||||
Permission.check_permissions(self.request.user, [permission_folder_view])
|
||||
Permission.check_permissions(
|
||||
self.request.user, [permission_folder_view]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_folder_view, self.request.user, folder)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_folder_view, self.request.user, folder
|
||||
)
|
||||
|
||||
return folder
|
||||
|
||||
@@ -216,9 +238,13 @@ class DocumentFolderListView(FolderListView):
|
||||
self.document = get_object_or_404(Document, pk=self.kwargs['pk'])
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_document_view])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_document_view]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_document_view, request.user, self.document)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_document_view, request.user, self.document
|
||||
)
|
||||
|
||||
return super(DocumentFolderListView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
@@ -228,7 +254,14 @@ class DocumentFolderListView(FolderListView):
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'extra_columns': [
|
||||
{'name': _('Documents'), 'attribute': encapsulate(lambda instance: FolderListView.get_document_count(instance=instance, user=self.request.user))},
|
||||
{
|
||||
'name': _('Documents'),
|
||||
'attribute': encapsulate(
|
||||
lambda instance: FolderListView.get_document_count(
|
||||
instance=instance, user=self.request.user
|
||||
)
|
||||
)
|
||||
},
|
||||
],
|
||||
'hide_link': True,
|
||||
'object': self.document,
|
||||
|
||||
@@ -7,7 +7,9 @@ from common.utils import encapsulate
|
||||
from navigation import SourceColumn
|
||||
|
||||
from .classes import Property, PropertyNamespace, PIPNotFound, VirtualEnv
|
||||
from .links import link_menu_link, link_namespace_details, link_namespace_list
|
||||
from .links import (
|
||||
link_menu_link, link_namespace_details, link_namespace_list
|
||||
)
|
||||
|
||||
|
||||
class InstallationApp(MayanAppConfig):
|
||||
@@ -17,21 +19,35 @@ class InstallationApp(MayanAppConfig):
|
||||
def ready(self):
|
||||
super(InstallationApp, self).ready()
|
||||
|
||||
SourceColumn(source=PropertyNamespace, label=_('Label'), attribute='label')
|
||||
SourceColumn(source=PropertyNamespace, label=_('Items'), attribute=encapsulate(lambda entry: len(entry.get_properties())))
|
||||
SourceColumn(
|
||||
source=PropertyNamespace, label=_('Label'), attribute='label'
|
||||
)
|
||||
SourceColumn(
|
||||
source=PropertyNamespace, label=_('Items'),
|
||||
attribute=encapsulate(lambda entry: len(entry.get_properties()))
|
||||
)
|
||||
|
||||
SourceColumn(source=Property, label=_('Label'), attribute='label')
|
||||
SourceColumn(source=Property, label=_('Value'), attribute='value')
|
||||
|
||||
menu_object.bind_links(links=[link_namespace_details], sources=[PropertyNamespace])
|
||||
menu_secondary.bind_links(links=[link_namespace_list], sources=['installation:namespace_list', PropertyNamespace])
|
||||
menu_object.bind_links(
|
||||
links=[link_namespace_details], sources=[PropertyNamespace]
|
||||
)
|
||||
menu_secondary.bind_links(
|
||||
links=[link_namespace_list],
|
||||
sources=['installation:namespace_list', PropertyNamespace]
|
||||
)
|
||||
menu_tools.bind_links(links=[link_menu_link])
|
||||
|
||||
namespace = PropertyNamespace('venv', _('VirtualEnv'))
|
||||
try:
|
||||
venv = VirtualEnv()
|
||||
except PIPNotFound:
|
||||
namespace.add_property('pip', 'pip', _('pip not found.'), report=True)
|
||||
namespace.add_property(
|
||||
'pip', 'pip', _('pip not found.'), report=True
|
||||
)
|
||||
else:
|
||||
for item, version, result in venv.get_results():
|
||||
namespace.add_property(item, '%s (%s)' % (item, version), result, report=True)
|
||||
namespace.add_property(
|
||||
item, '%s (%s)' % (item, version), result, report=True
|
||||
)
|
||||
|
||||
@@ -108,7 +108,8 @@ class VirtualEnv(object):
|
||||
# has no version number
|
||||
return Dependency(string, version=None, standard=True)
|
||||
else:
|
||||
version = version.split('#')[0].split(' ')[1] # Get rid of '#egg' and '-e'
|
||||
# Get rid of '#egg' and '-e'
|
||||
version = version.split('#')[0].split(' ')[1]
|
||||
return Dependency(package, version, standard=False)
|
||||
else:
|
||||
return Dependency(package, version, standard=True)
|
||||
@@ -146,9 +147,12 @@ class VirtualEnv(object):
|
||||
if item.version == installed_packages['%s-dev' % name.replace('-', '_')].version:
|
||||
status = item.version
|
||||
else:
|
||||
status = installed_packages['%s-dev' % name.replace('-', '_')].version
|
||||
status = installed_packages[
|
||||
'%s-dev' % name.replace('-', '_')
|
||||
].version
|
||||
except KeyError:
|
||||
# Not installed package found matching with name matchin requirement
|
||||
# Not installed package found matching with name matching
|
||||
# requirement
|
||||
status = False
|
||||
|
||||
yield name, item.version, status
|
||||
|
||||
@@ -6,6 +6,17 @@ from navigation import Link
|
||||
|
||||
from .permissions import permission_installation_details
|
||||
|
||||
link_menu_link = Link(icon='fa fa-check-square-o', permissions=[permission_installation_details], text=_('Installation details'), view='installation:namespace_list')
|
||||
link_namespace_details = Link(permissions=[permission_installation_details], text=_('Details'), view='installation:namespace_details', args='object.id')
|
||||
link_namespace_list = Link(permissions=[permission_installation_details], text=_('Installation property namespaces'), view='installation:namespace_list')
|
||||
link_menu_link = Link(
|
||||
icon='fa fa-check-square-o',
|
||||
permissions=[permission_installation_details],
|
||||
text=_('Installation details'), view='installation:namespace_list'
|
||||
)
|
||||
link_namespace_details = Link(
|
||||
permissions=[permission_installation_details], text=_('Details'),
|
||||
view='installation:namespace_details', args='object.id'
|
||||
)
|
||||
link_namespace_list = Link(
|
||||
permissions=[permission_installation_details],
|
||||
text=_('Installation property namespaces'),
|
||||
view='installation:namespace_list'
|
||||
)
|
||||
|
||||
@@ -5,4 +5,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
from permissions import PermissionNamespace
|
||||
|
||||
namespace = PermissionNamespace('installation', _('Installation'))
|
||||
permission_installation_details = namespace.add_permission(name='installation_details', label=_('View installation environment details'))
|
||||
permission_installation_details = namespace.add_permission(
|
||||
name='installation_details',
|
||||
label=_('View installation environment details')
|
||||
)
|
||||
|
||||
@@ -5,5 +5,8 @@ from django.conf.urls import patterns, url
|
||||
urlpatterns = patterns(
|
||||
'installation.views',
|
||||
url(r'^$', 'namespace_list', name='namespace_list'),
|
||||
url(r'^(?P<namespace_id>\w+)/details/$', 'namespace_details', name='namespace_details'),
|
||||
url(
|
||||
r'^(?P<namespace_id>\w+)/details/$', 'namespace_details',
|
||||
name='namespace_details'
|
||||
),
|
||||
)
|
||||
|
||||
@@ -11,7 +11,9 @@ from .permissions import permission_installation_details
|
||||
|
||||
|
||||
def namespace_list(request):
|
||||
Permission.check_permissions(request.user, [permission_installation_details])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_installation_details]
|
||||
)
|
||||
|
||||
return render_to_response('appearance/generic_list.html', {
|
||||
'object_list': PropertyNamespace.get_all(),
|
||||
@@ -21,7 +23,9 @@ def namespace_list(request):
|
||||
|
||||
|
||||
def namespace_details(request, namespace_id):
|
||||
Permission.check_permissions(request.user, [permission_installation_details])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_installation_details]
|
||||
)
|
||||
|
||||
namespace = PropertyNamespace.get(namespace_id)
|
||||
object_list = namespace.get_properties()
|
||||
|
||||
@@ -41,10 +41,41 @@ class LinkingApp(MayanAppConfig):
|
||||
)
|
||||
)
|
||||
|
||||
menu_facet.bind_links(links=[link_smart_link_instances_for_document], sources=[Document])
|
||||
menu_object.bind_links(links=[link_smart_link_condition_edit, link_smart_link_condition_delete], sources=[SmartLinkCondition])
|
||||
menu_object.bind_links(links=[link_smart_link_edit, link_smart_link_document_types, link_smart_link_condition_list, link_acl_list, link_smart_link_delete], sources=[SmartLink])
|
||||
menu_object.bind_links(links=[link_smart_link_instance_view], sources=[ResolvedSmartLink])
|
||||
menu_secondary.bind_links(links=[link_smart_link_list, link_smart_link_create], sources=[SmartLink, 'linking:smart_link_list', 'linking:smart_link_create'])
|
||||
menu_facet.bind_links(
|
||||
links=[link_smart_link_instances_for_document],
|
||||
sources=[Document]
|
||||
)
|
||||
menu_object.bind_links(
|
||||
links=[
|
||||
link_smart_link_condition_edit,
|
||||
link_smart_link_condition_delete
|
||||
], sources=[SmartLinkCondition]
|
||||
)
|
||||
menu_object.bind_links(
|
||||
links=[
|
||||
link_smart_link_edit, link_smart_link_document_types,
|
||||
link_smart_link_condition_list, link_acl_list,
|
||||
link_smart_link_delete
|
||||
], sources=[SmartLink]
|
||||
)
|
||||
menu_object.bind_links(
|
||||
links=[link_smart_link_instance_view],
|
||||
sources=[ResolvedSmartLink]
|
||||
)
|
||||
menu_secondary.bind_links(
|
||||
links=[link_smart_link_list, link_smart_link_create],
|
||||
sources=[
|
||||
SmartLink, 'linking:smart_link_list',
|
||||
'linking:smart_link_create'
|
||||
]
|
||||
)
|
||||
menu_setup.bind_links(links=[link_smart_link_setup])
|
||||
menu_sidebar.bind_links(links=[link_smart_link_condition_create], sources=['linking:smart_link_condition_list', 'linking:smart_link_condition_create', 'linking:smart_link_condition_edit', 'linking:smart_link_condition_delete'])
|
||||
menu_sidebar.bind_links(
|
||||
links=[link_smart_link_condition_create],
|
||||
sources=[
|
||||
'linking:smart_link_condition_list',
|
||||
'linking:smart_link_condition_create',
|
||||
'linking:smart_link_condition_edit',
|
||||
'linking:smart_link_condition_delete'
|
||||
]
|
||||
)
|
||||
|
||||
@@ -12,7 +12,14 @@ from .models import SmartLink, SmartLinkCondition
|
||||
class SmartLinkForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SmartLinkForm, self).__init__(*args, **kwargs)
|
||||
self.fields['dynamic_label'].help_text = ' '.join([unicode(self.fields['dynamic_label'].help_text), ModelAttribute.help_text_for(Document, type_names=['field', 'related', 'property'])])
|
||||
self.fields['dynamic_label'].help_text = ' '.join(
|
||||
[
|
||||
unicode(self.fields['dynamic_label'].help_text),
|
||||
ModelAttribute.help_text_for(
|
||||
Document, type_names=['field', 'related', 'property']
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
class Meta:
|
||||
fields = ('label', 'dynamic_label', 'enabled')
|
||||
@@ -22,8 +29,19 @@ class SmartLinkForm(forms.ModelForm):
|
||||
class SmartLinkConditionForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SmartLinkConditionForm, self).__init__(*args, **kwargs)
|
||||
self.fields['foreign_document_data'] = forms.ChoiceField(choices=ModelAttribute.get_choices_for(Document, type_names=['field', 'query']), label=_('Foreign document attribute'))
|
||||
self.fields['expression'].help_text = ' '.join([unicode(self.fields['expression'].help_text), ModelAttribute.help_text_for(Document, type_names=['field', 'related', 'property'])])
|
||||
self.fields['foreign_document_data'] = forms.ChoiceField(
|
||||
choices=ModelAttribute.get_choices_for(
|
||||
Document, type_names=['field', 'query']
|
||||
), label=_('Foreign document attribute')
|
||||
)
|
||||
self.fields['expression'].help_text = ' '.join(
|
||||
[
|
||||
unicode(self.fields['expression'].help_text),
|
||||
ModelAttribute.help_text_for(
|
||||
Document, type_names=['field', 'related', 'property']
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = SmartLinkCondition
|
||||
|
||||
@@ -10,15 +10,54 @@ from .permissions import (
|
||||
permission_smart_link_edit, permission_smart_link_view
|
||||
)
|
||||
|
||||
link_smart_link_condition_create = Link(permissions=[permission_smart_link_edit], text=_('Create condition'), view='linking:smart_link_condition_create', args='object.pk')
|
||||
link_smart_link_condition_delete = Link(permissions=[permission_smart_link_edit], tags='dangerous', text=_('Delete'), view='linking:smart_link_condition_delete', args='resolved_object.pk')
|
||||
link_smart_link_condition_edit = Link(permissions=[permission_smart_link_edit], text=_('Edit'), view='linking:smart_link_condition_edit', args='resolved_object.pk')
|
||||
link_smart_link_condition_list = Link(permissions=[permission_smart_link_edit], text=_('Conditions'), view='linking:smart_link_condition_list', args='object.pk')
|
||||
link_smart_link_create = Link(permissions=[permission_smart_link_create], text=_('Create new smart link'), view='linking:smart_link_create')
|
||||
link_smart_link_delete = Link(permissions=[permission_smart_link_delete], tags='dangerous', text=_('Delete'), view='linking:smart_link_delete', args='object.pk')
|
||||
link_smart_link_document_types = Link(permissions=[permission_smart_link_edit], text=_('Document types'), view='linking:smart_link_document_types', args='object.pk')
|
||||
link_smart_link_edit = Link(permissions=[permission_smart_link_edit], text=_('Edit'), view='linking:smart_link_edit', args='object.pk')
|
||||
link_smart_link_instance_view = Link(permissions=[permission_smart_link_view], text=_('Documents'), view='linking:smart_link_instance_view', args=['document.pk', 'object.pk'])
|
||||
link_smart_link_instances_for_document = Link(permissions=[permission_document_view], text=_('Smart links'), view='linking:smart_link_instances_for_document', args='object.pk')
|
||||
link_smart_link_list = Link(permissions=[permission_smart_link_create], text=_('Smart links'), view='linking:smart_link_list')
|
||||
link_smart_link_setup = Link(icon='fa fa-link', permissions=[permission_smart_link_create], text=_('Smart links'), view='linking:smart_link_list')
|
||||
link_smart_link_condition_create = Link(
|
||||
permissions=[permission_smart_link_edit], text=_('Create condition'),
|
||||
view='linking:smart_link_condition_create', args='object.pk'
|
||||
)
|
||||
link_smart_link_condition_delete = Link(
|
||||
permissions=[permission_smart_link_edit], tags='dangerous',
|
||||
text=_('Delete'), view='linking:smart_link_condition_delete',
|
||||
args='resolved_object.pk'
|
||||
)
|
||||
link_smart_link_condition_edit = Link(
|
||||
permissions=[permission_smart_link_edit], text=_('Edit'),
|
||||
view='linking:smart_link_condition_edit', args='resolved_object.pk'
|
||||
)
|
||||
link_smart_link_condition_list = Link(
|
||||
permissions=[permission_smart_link_edit], text=_('Conditions'),
|
||||
view='linking:smart_link_condition_list', args='object.pk'
|
||||
)
|
||||
link_smart_link_create = Link(
|
||||
permissions=[permission_smart_link_create],
|
||||
text=_('Create new smart link'), view='linking:smart_link_create'
|
||||
)
|
||||
link_smart_link_delete = Link(
|
||||
permissions=[permission_smart_link_delete], tags='dangerous',
|
||||
text=_('Delete'), view='linking:smart_link_delete', args='object.pk'
|
||||
)
|
||||
link_smart_link_document_types = Link(
|
||||
permissions=[permission_smart_link_edit], text=_('Document types'),
|
||||
view='linking:smart_link_document_types', args='object.pk'
|
||||
)
|
||||
link_smart_link_edit = Link(
|
||||
permissions=[permission_smart_link_edit], text=_('Edit'),
|
||||
view='linking:smart_link_edit', args='object.pk'
|
||||
)
|
||||
link_smart_link_instance_view = Link(
|
||||
permissions=[permission_smart_link_view], text=_('Documents'),
|
||||
view='linking:smart_link_instance_view', args=[
|
||||
'document.pk', 'object.pk'
|
||||
]
|
||||
)
|
||||
link_smart_link_instances_for_document = Link(
|
||||
permissions=[permission_document_view], text=_('Smart links'),
|
||||
view='linking:smart_link_instances_for_document', args='object.pk'
|
||||
)
|
||||
link_smart_link_list = Link(
|
||||
permissions=[permission_smart_link_create], text=_('Smart links'),
|
||||
view='linking:smart_link_list'
|
||||
)
|
||||
link_smart_link_setup = Link(
|
||||
icon='fa fa-link', permissions=[permission_smart_link_create],
|
||||
text=_('Smart links'), view='linking:smart_link_list'
|
||||
)
|
||||
|
||||
@@ -15,9 +15,16 @@ from .literals import (
|
||||
@python_2_unicode_compatible
|
||||
class SmartLink(models.Model):
|
||||
label = models.CharField(max_length=96, verbose_name=_('Label'))
|
||||
dynamic_label = models.CharField(blank=True, max_length=96, help_text=_('This expression will be evaluated against the current selected document.'), verbose_name=_('Dynamic label'))
|
||||
dynamic_label = models.CharField(
|
||||
blank=True, max_length=96, help_text=_(
|
||||
'This expression will be evaluated against the current selected '
|
||||
'document.'
|
||||
), verbose_name=_('Dynamic label')
|
||||
)
|
||||
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
|
||||
document_types = models.ManyToManyField(DocumentType, verbose_name=_('Document types'))
|
||||
document_types = models.ManyToManyField(
|
||||
DocumentType, verbose_name=_('Document types')
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
@@ -27,19 +34,30 @@ class SmartLink(models.Model):
|
||||
try:
|
||||
return eval(self.dynamic_label, {'document': document})
|
||||
except Exception as exception:
|
||||
return Exception(_('Error generating dynamic label; %s' % unicode(exception)))
|
||||
return Exception(
|
||||
_(
|
||||
'Error generating dynamic label; %s' % unicode(exception)
|
||||
)
|
||||
)
|
||||
else:
|
||||
return self.label
|
||||
|
||||
def get_linked_document_for(self, document):
|
||||
if document.document_type.pk not in self.document_types.values_list('pk', flat=True):
|
||||
raise Exception(_('This smart link is not allowed for the selected document\'s type.'))
|
||||
raise Exception(
|
||||
_(
|
||||
'This smart link is not allowed for the selected '
|
||||
'document\'s type.'
|
||||
)
|
||||
)
|
||||
|
||||
smart_link_query = Q()
|
||||
|
||||
for condition in self.conditions.filter(enabled=True):
|
||||
condition_query = Q(**{
|
||||
'%s__%s' % (condition.foreign_document_data, condition.operator): eval(condition.expression, {'document': document})
|
||||
'%s__%s' % (
|
||||
condition.foreign_document_data, condition.operator
|
||||
): eval(condition.expression, {'document': document})
|
||||
})
|
||||
if condition.negated:
|
||||
condition_query = ~condition_query
|
||||
@@ -55,7 +73,9 @@ class SmartLink(models.Model):
|
||||
return Document.objects.none()
|
||||
|
||||
def resolve_for(self, document):
|
||||
return ResolvedSmartLink(smart_link=self, queryset=self.get_linked_document_for(document))
|
||||
return ResolvedSmartLink(
|
||||
smart_link=self, queryset=self.get_linked_document_for(document)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Smart link')
|
||||
@@ -69,16 +89,36 @@ class ResolvedSmartLink(SmartLink):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class SmartLinkCondition(models.Model):
|
||||
smart_link = models.ForeignKey(SmartLink, related_name='conditions', verbose_name=_('Smart link'))
|
||||
inclusion = models.CharField(choices=INCLUSION_CHOICES, default=INCLUSION_AND, help_text=_('The inclusion is ignored for the first item.'), max_length=16)
|
||||
foreign_document_data = models.CharField(help_text=_('This represents the metadata of all other documents.'), max_length=128, verbose_name=_('Foreign document attribute'))
|
||||
smart_link = models.ForeignKey(
|
||||
SmartLink, related_name='conditions', verbose_name=_('Smart link')
|
||||
)
|
||||
inclusion = models.CharField(
|
||||
choices=INCLUSION_CHOICES, default=INCLUSION_AND,
|
||||
help_text=_('The inclusion is ignored for the first item.'),
|
||||
max_length=16
|
||||
)
|
||||
foreign_document_data = models.CharField(
|
||||
help_text=_('This represents the metadata of all other documents.'),
|
||||
max_length=128, verbose_name=_('Foreign document attribute')
|
||||
)
|
||||
operator = models.CharField(choices=OPERATOR_CHOICES, max_length=16)
|
||||
expression = models.TextField(help_text=_('This expression will be evaluated against the current document.'), verbose_name=_('Expression'))
|
||||
negated = models.BooleanField(default=False, help_text=_('Inverts the logic of the operator.'), verbose_name=_('Negated'))
|
||||
expression = models.TextField(
|
||||
help_text=_(
|
||||
'This expression will be evaluated against the current document.'
|
||||
), verbose_name=_('Expression')
|
||||
)
|
||||
negated = models.BooleanField(
|
||||
default=False, help_text=_('Inverts the logic of the operator.'),
|
||||
verbose_name=_('Negated')
|
||||
)
|
||||
enabled = models.BooleanField(default=True, verbose_name=_('Enabled'))
|
||||
|
||||
def __str__(self):
|
||||
return '%s foreign %s %s %s %s' % (self.get_inclusion_display(), self.foreign_document_data, _('not') if self.negated else '', self.get_operator_display(), self.expression)
|
||||
return '%s foreign %s %s %s %s' % (
|
||||
self.get_inclusion_display(),
|
||||
self.foreign_document_data, _('not') if self.negated else '',
|
||||
self.get_operator_display(), self.expression
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('Link condition')
|
||||
|
||||
@@ -6,7 +6,15 @@ from permissions import PermissionNamespace
|
||||
|
||||
namespace = PermissionNamespace('linking', _('Smart links'))
|
||||
|
||||
permission_smart_link_view = namespace.add_permission(name='smart_link_view', label=_('View existing smart links'))
|
||||
permission_smart_link_create = namespace.add_permission(name='smart_link_create', label=_('Create new smart links'))
|
||||
permission_smart_link_delete = namespace.add_permission(name='smart_link_delete', label=_('Delete smart links'))
|
||||
permission_smart_link_edit = namespace.add_permission(name='smart_link_edit', label=_('Edit smart links'))
|
||||
permission_smart_link_view = namespace.add_permission(
|
||||
name='smart_link_view', label=_('View existing smart links')
|
||||
)
|
||||
permission_smart_link_create = namespace.add_permission(
|
||||
name='smart_link_create', label=_('Create new smart links')
|
||||
)
|
||||
permission_smart_link_delete = namespace.add_permission(
|
||||
name='smart_link_delete', label=_('Delete smart links')
|
||||
)
|
||||
permission_smart_link_edit = namespace.add_permission(
|
||||
name='smart_link_edit', label=_('Edit smart links')
|
||||
)
|
||||
|
||||
@@ -9,17 +9,47 @@ from .views import (
|
||||
|
||||
urlpatterns = patterns(
|
||||
'linking.views',
|
||||
url(r'^document/(?P<pk>\d+)/list/$', DocumentSmartLinkListView.as_view(), name='smart_link_instances_for_document'),
|
||||
url(r'^document/(?P<document_pk>\d+)/(?P<smart_link_pk>\d+)/$', ResolvedSmartLinkView.as_view(), name='smart_link_instance_view'),
|
||||
url(
|
||||
r'^document/(?P<pk>\d+)/list/$', DocumentSmartLinkListView.as_view(),
|
||||
name='smart_link_instances_for_document'
|
||||
),
|
||||
url(
|
||||
r'^document/(?P<document_pk>\d+)/(?P<smart_link_pk>\d+)/$',
|
||||
ResolvedSmartLinkView.as_view(), name='smart_link_instance_view'
|
||||
),
|
||||
|
||||
url(r'^setup/list/$', SmartLinkListView.as_view(), name='smart_link_list'),
|
||||
url(
|
||||
r'^setup/list/$', SmartLinkListView.as_view(), name='smart_link_list'
|
||||
),
|
||||
url(r'^setup/create/$', 'smart_link_create', name='smart_link_create'),
|
||||
url(r'^setup/(?P<smart_link_pk>\d+)/delete/$', 'smart_link_delete', name='smart_link_delete'),
|
||||
url(r'^setup/(?P<smart_link_pk>\d+)/edit/$', 'smart_link_edit', name='smart_link_edit'),
|
||||
url(r'^setup/(?P<pk>\d+)/document_types/$', SetupSmartLinkDocumentTypesView.as_view(), name='smart_link_document_types'),
|
||||
url(
|
||||
r'^setup/(?P<smart_link_pk>\d+)/delete/$', 'smart_link_delete',
|
||||
name='smart_link_delete'
|
||||
),
|
||||
url(
|
||||
r'^setup/(?P<smart_link_pk>\d+)/edit/$', 'smart_link_edit',
|
||||
name='smart_link_edit'
|
||||
),
|
||||
url(
|
||||
r'^setup/(?P<pk>\d+)/document_types/$',
|
||||
SetupSmartLinkDocumentTypesView.as_view(),
|
||||
name='smart_link_document_types'
|
||||
),
|
||||
|
||||
url(r'^setup/(?P<smart_link_pk>\d+)/condition/list/$', 'smart_link_condition_list', name='smart_link_condition_list'),
|
||||
url(r'^setup/(?P<smart_link_pk>\d+)/condition/create/$', 'smart_link_condition_create', name='smart_link_condition_create'),
|
||||
url(r'^setup/smart_link/condition/(?P<smart_link_condition_pk>\d+)/edit/$', 'smart_link_condition_edit', name='smart_link_condition_edit'),
|
||||
url(r'^setup/smart_link/condition/(?P<smart_link_condition_pk>\d+)/delete/$', 'smart_link_condition_delete', name='smart_link_condition_delete'),
|
||||
url(
|
||||
r'^setup/(?P<smart_link_pk>\d+)/condition/list/$',
|
||||
'smart_link_condition_list', name='smart_link_condition_list'
|
||||
),
|
||||
url(
|
||||
r'^setup/(?P<smart_link_pk>\d+)/condition/create/$',
|
||||
'smart_link_condition_create', name='smart_link_condition_create'
|
||||
),
|
||||
url(
|
||||
r'^setup/smart_link/condition/(?P<smart_link_condition_pk>\d+)/edit/$',
|
||||
'smart_link_condition_edit', name='smart_link_condition_edit'
|
||||
),
|
||||
url(
|
||||
r'^setup/smart_link/condition/(?P<smart_link_condition_pk>\d+)/delete/$',
|
||||
'smart_link_condition_delete', name='smart_link_condition_delete'
|
||||
),
|
||||
)
|
||||
|
||||
@@ -43,19 +43,29 @@ class SetupSmartLinkDocumentTypesView(AssignRemoveView):
|
||||
return get_object_or_404(SmartLink, pk=self.kwargs['pk'])
|
||||
|
||||
def left_list(self):
|
||||
return AssignRemoveView.generate_choices(DocumentType.objects.exclude(pk__in=self.get_object().document_types.all()))
|
||||
return AssignRemoveView.generate_choices(
|
||||
DocumentType.objects.exclude(
|
||||
pk__in=self.get_object().document_types.all()
|
||||
)
|
||||
)
|
||||
|
||||
def right_list(self):
|
||||
return AssignRemoveView.generate_choices(self.get_object().document_types.all())
|
||||
return AssignRemoveView.generate_choices(
|
||||
self.get_object().document_types.all()
|
||||
)
|
||||
|
||||
def remove(self, item):
|
||||
self.get_object().document_types.remove(item)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
data = super(SetupSmartLinkDocumentTypesView, self).get_context_data(**kwargs)
|
||||
data = super(
|
||||
SetupSmartLinkDocumentTypesView, self
|
||||
).get_context_data(**kwargs)
|
||||
data.update({
|
||||
'object': self.get_object(),
|
||||
'title': _('Document type for which to enable smart link: %s') % self.get_object(),
|
||||
'title': _(
|
||||
'Document type for which to enable smart link: %s'
|
||||
) % self.get_object(),
|
||||
})
|
||||
|
||||
return data
|
||||
@@ -63,20 +73,34 @@ class SetupSmartLinkDocumentTypesView(AssignRemoveView):
|
||||
|
||||
class ResolvedSmartLinkView(DocumentListView):
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.document = get_object_or_404(Document, pk=self.kwargs['document_pk'])
|
||||
self.smart_link = get_object_or_404(SmartLink, pk=self.kwargs['smart_link_pk'])
|
||||
self.document = get_object_or_404(
|
||||
Document, pk=self.kwargs['document_pk']
|
||||
)
|
||||
self.smart_link = get_object_or_404(
|
||||
SmartLink, pk=self.kwargs['smart_link_pk']
|
||||
)
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_document_view])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_document_view]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_document_view, request.user, self.document)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_document_view, request.user, self.document
|
||||
)
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_smart_link_view])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_smart_link_view]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_smart_link_view, request.user, self.smart_link)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_smart_link_view, request.user, self.smart_link
|
||||
)
|
||||
|
||||
return super(ResolvedSmartLinkView, self).dispatch(request, *args, **kwargs)
|
||||
return super(
|
||||
ResolvedSmartLinkView, self
|
||||
).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_document_queryset(self):
|
||||
try:
|
||||
@@ -85,7 +109,9 @@ class ResolvedSmartLinkView(DocumentListView):
|
||||
queryset = Document.objects.none()
|
||||
|
||||
if self.request.user.is_staff or self.request.user.is_superuser:
|
||||
messages.error(self.request, _('Smart link query error: %s' % exception))
|
||||
messages.error(
|
||||
self.request, _('Smart link query error: %s' % exception)
|
||||
)
|
||||
|
||||
return queryset
|
||||
|
||||
@@ -93,7 +119,10 @@ class ResolvedSmartLinkView(DocumentListView):
|
||||
return {
|
||||
'hide_links': True,
|
||||
'object': self.document,
|
||||
'title': _('Documents in smart link "%(smart_link)s" as relation to "%(document)s"') % {
|
||||
'title': _(
|
||||
'Documents in smart link "%(smart_link)s" as relation to '
|
||||
'"%(document)s"'
|
||||
) % {
|
||||
'document': self.document,
|
||||
'smart_link': self.smart_link.get_dynamic_label(self.document),
|
||||
}
|
||||
@@ -114,7 +143,11 @@ class SmartLinkListView(SingleObjectListView):
|
||||
return {
|
||||
'extra_columns': [
|
||||
{'name': _('Dynamic label'), 'attribute': 'dynamic_label'},
|
||||
{'name': _('Enabled'), 'attribute': encapsulate(lambda instance: two_state_template(instance.enabled))},
|
||||
{
|
||||
'name': _('Enabled'), 'attribute': encapsulate(
|
||||
lambda instance: two_state_template(instance.enabled)
|
||||
)
|
||||
},
|
||||
],
|
||||
'hide_link': True,
|
||||
'title': _('Smart links'),
|
||||
@@ -126,20 +159,34 @@ class DocumentSmartLinkListView(SmartLinkListView):
|
||||
self.document = get_object_or_404(Document, pk=self.kwargs['pk'])
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, (permission_document_view,))
|
||||
Permission.check_permissions(
|
||||
request.user, (permission_document_view,)
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_permissions(permission_document_view, request.user, self.document)
|
||||
AccessControlList.objects.check_permissions(
|
||||
permission_document_view, request.user, self.document
|
||||
)
|
||||
|
||||
return super(DocumentSmartLinkListView, self).dispatch(request, *args, **kwargs)
|
||||
return super(
|
||||
DocumentSmartLinkListView, self
|
||||
).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_smart_link_queryset(self):
|
||||
return ResolvedSmartLink.objects.filter(document_types=self.document.document_type, enabled=True)
|
||||
return ResolvedSmartLink.objects.filter(
|
||||
document_types=self.document.document_type, enabled=True
|
||||
)
|
||||
|
||||
def get_extra_context(self):
|
||||
return {
|
||||
'document': self.document,
|
||||
'extra_columns': (
|
||||
{'name': _('Label'), 'attribute': encapsulate(lambda smart_link: smart_link.get_dynamic_label(self.document))},
|
||||
{
|
||||
'name': _('Label'), 'attribute': encapsulate(
|
||||
lambda smart_link: smart_link.get_dynamic_label(
|
||||
self.document
|
||||
)
|
||||
)
|
||||
},
|
||||
),
|
||||
'hide_object': True,
|
||||
'hide_link': True,
|
||||
@@ -149,13 +196,19 @@ class DocumentSmartLinkListView(SmartLinkListView):
|
||||
|
||||
|
||||
def smart_link_create(request):
|
||||
Permission.check_permissions(request.user, [permission_smart_link_create])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_smart_link_create]
|
||||
)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = SmartLinkForm(request.POST)
|
||||
if form.is_valid():
|
||||
document_group = form.save()
|
||||
messages.success(request, _('Smart link: %s created successfully.') % document_group)
|
||||
messages.success(
|
||||
request, _(
|
||||
'Smart link: %s created successfully.'
|
||||
) % document_group
|
||||
)
|
||||
return HttpResponseRedirect(reverse('linking:smart_link_list'))
|
||||
else:
|
||||
form = SmartLinkForm()
|
||||
@@ -170,15 +223,23 @@ def smart_link_edit(request, smart_link_pk):
|
||||
smart_link = get_object_or_404(SmartLink, pk=smart_link_pk)
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_smart_link_edit])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_smart_link_edit]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_smart_link_edit, request.user, smart_link)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_smart_link_edit, request.user, smart_link
|
||||
)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = SmartLinkForm(request.POST, instance=smart_link)
|
||||
if form.is_valid():
|
||||
smart_link = form.save()
|
||||
messages.success(request, _('Smart link: %s edited successfully.') % smart_link)
|
||||
messages.success(
|
||||
request, _(
|
||||
'Smart link: %s edited successfully.'
|
||||
) % smart_link
|
||||
)
|
||||
return HttpResponseRedirect(reverse('linking:smart_link_list'))
|
||||
else:
|
||||
form = SmartLinkForm(instance=smart_link)
|
||||
@@ -194,22 +255,39 @@ def smart_link_delete(request, smart_link_pk):
|
||||
smart_link = get_object_or_404(SmartLink, pk=smart_link_pk)
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_smart_link_delete])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_smart_link_delete]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access(permission_smart_link_delete, request.user, smart_link)
|
||||
AccessControlList.objects.check_access(
|
||||
permission_smart_link_delete, request.user, smart_link
|
||||
)
|
||||
|
||||
next = request.POST.get('next', request.GET.get('next', request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))))
|
||||
previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))))
|
||||
next = request.POST.get(
|
||||
'next', request.GET.get('next', request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL)))
|
||||
)
|
||||
previous = request.POST.get(
|
||||
'previous', request.GET.get('previous', request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL)))
|
||||
)
|
||||
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
smart_link.delete()
|
||||
messages.success(request, _('Smart link: %s deleted successfully.') % smart_link)
|
||||
messages.success(
|
||||
request, _(
|
||||
'Smart link: %s deleted successfully.'
|
||||
) % smart_link
|
||||
)
|
||||
except Exception as exception:
|
||||
messages.error(request, _('Error deleting smart link: %(smart_link)s; %(exception)s.') % {
|
||||
'smart_link': smart_link,
|
||||
'exception': exception
|
||||
})
|
||||
messages.error(
|
||||
request, _(
|
||||
'Error deleting smart link: %(smart_link)s; '
|
||||
'%(exception)s.'
|
||||
) % {
|
||||
'smart_link': smart_link,
|
||||
'exception': exception
|
||||
}
|
||||
)
|
||||
return HttpResponseRedirect(next)
|
||||
|
||||
return render_to_response('appearance/generic_confirm.html', {
|
||||
@@ -225,15 +303,24 @@ def smart_link_condition_list(request, smart_link_pk):
|
||||
smart_link = get_object_or_404(SmartLink, pk=smart_link_pk)
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_smart_link_edit])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_smart_link_edit]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access([permission_smart_link_edit], request.user, smart_link)
|
||||
AccessControlList.objects.check_access(
|
||||
[permission_smart_link_edit], request.user, smart_link
|
||||
)
|
||||
|
||||
return render_to_response('appearance/generic_list.html', {
|
||||
'title': _('Conditions for smart link: %s') % smart_link,
|
||||
'object_list': smart_link.conditions.all(),
|
||||
'extra_columns': [
|
||||
{'name': _('Enabled'), 'attribute': encapsulate(lambda x: two_state_template(x.enabled))},
|
||||
{
|
||||
'name': _('Enabled'),
|
||||
'attribute': encapsulate(
|
||||
lambda x: two_state_template(x.enabled)
|
||||
)
|
||||
},
|
||||
],
|
||||
'hide_link': True,
|
||||
'object': smart_link,
|
||||
@@ -244,9 +331,13 @@ def smart_link_condition_create(request, smart_link_pk):
|
||||
smart_link = get_object_or_404(SmartLink, pk=smart_link_pk)
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_smart_link_edit])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_smart_link_edit]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access([permission_smart_link_edit], request.user, smart_link)
|
||||
AccessControlList.objects.check_access(
|
||||
[permission_smart_link_edit], request.user, smart_link
|
||||
)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = SmartLinkConditionForm(data=request.POST)
|
||||
@@ -254,8 +345,16 @@ def smart_link_condition_create(request, smart_link_pk):
|
||||
new_smart_link_condition = form.save(commit=False)
|
||||
new_smart_link_condition.smart_link = smart_link
|
||||
new_smart_link_condition.save()
|
||||
messages.success(request, _('Smart link condition: "%s" created successfully.') % new_smart_link_condition)
|
||||
return HttpResponseRedirect(reverse('linking:smart_link_condition_list', args=[smart_link.pk]))
|
||||
messages.success(
|
||||
request, _(
|
||||
'Smart link condition: "%s" created successfully.'
|
||||
) % new_smart_link_condition
|
||||
)
|
||||
return HttpResponseRedirect(
|
||||
reverse(
|
||||
'linking:smart_link_condition_list', args=[smart_link.pk]
|
||||
)
|
||||
)
|
||||
else:
|
||||
form = SmartLinkConditionForm()
|
||||
|
||||
@@ -267,21 +366,34 @@ def smart_link_condition_create(request, smart_link_pk):
|
||||
|
||||
|
||||
def smart_link_condition_edit(request, smart_link_condition_pk):
|
||||
smart_link_condition = get_object_or_404(SmartLinkCondition, pk=smart_link_condition_pk)
|
||||
smart_link_condition = get_object_or_404(
|
||||
SmartLinkCondition, pk=smart_link_condition_pk
|
||||
)
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_smart_link_edit])
|
||||
Permission.check_permissions(
|
||||
request.user, [permission_smart_link_edit]
|
||||
)
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access([permission_smart_link_edit], request.user, smart_link_condition.smart_link)
|
||||
AccessControlList.objects.check_access(
|
||||
[permission_smart_link_edit], request.user,
|
||||
smart_link_condition.smart_link
|
||||
)
|
||||
|
||||
next = request.POST.get('next', request.GET.get('next', request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))))
|
||||
previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))))
|
||||
|
||||
if request.method == 'POST':
|
||||
form = SmartLinkConditionForm(request.POST, instance=smart_link_condition)
|
||||
form = SmartLinkConditionForm(
|
||||
request.POST, instance=smart_link_condition
|
||||
)
|
||||
if form.is_valid():
|
||||
smart_link_condition = form.save()
|
||||
messages.success(request, _('Smart link condition: "%s" edited successfully.') % smart_link_condition)
|
||||
messages.success(
|
||||
request, _(
|
||||
'Smart link condition: "%s" edited successfully.'
|
||||
) % smart_link_condition
|
||||
)
|
||||
return HttpResponseRedirect(next)
|
||||
else:
|
||||
form = SmartLinkConditionForm(instance=smart_link_condition)
|
||||
@@ -298,12 +410,17 @@ def smart_link_condition_edit(request, smart_link_condition_pk):
|
||||
|
||||
|
||||
def smart_link_condition_delete(request, smart_link_condition_pk):
|
||||
smart_link_condition = get_object_or_404(SmartLinkCondition, pk=smart_link_condition_pk)
|
||||
smart_link_condition = get_object_or_404(
|
||||
SmartLinkCondition, pk=smart_link_condition_pk
|
||||
)
|
||||
|
||||
try:
|
||||
Permission.check_permissions(request.user, [permission_smart_link_edit])
|
||||
except PermissionDenied:
|
||||
AccessControlList.objects.check_access([permission_smart_link_edit], request.user, smart_link_condition.smart_link)
|
||||
AccessControlList.objects.check_access(
|
||||
[permission_smart_link_edit], request.user,
|
||||
smart_link_condition.smart_link
|
||||
)
|
||||
|
||||
next = request.POST.get('next', request.GET.get('next', request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))))
|
||||
previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))))
|
||||
@@ -311,12 +428,21 @@ def smart_link_condition_delete(request, smart_link_condition_pk):
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
smart_link_condition.delete()
|
||||
messages.success(request, _('Smart link condition: "%s" deleted successfully.') % smart_link_condition)
|
||||
messages.success(
|
||||
request, _(
|
||||
'Smart link condition: "%s" deleted successfully.'
|
||||
) % smart_link_condition
|
||||
)
|
||||
except Exception as exception:
|
||||
messages.error(request, _('Error deleting smart link condition: %(smart_link_condition)s; %(exception)s.') % {
|
||||
'smart_link_condition': smart_link_condition,
|
||||
'exception': exception
|
||||
})
|
||||
messages.error(
|
||||
request, _(
|
||||
'Error deleting smart link condition: '
|
||||
'%(smart_link_condition)s; %(exception)s.'
|
||||
) % {
|
||||
'smart_link_condition': smart_link_condition,
|
||||
'exception': exception
|
||||
}
|
||||
)
|
||||
return HttpResponseRedirect(next)
|
||||
|
||||
return render_to_response('appearance/generic_confirm.html', {
|
||||
|
||||
@@ -42,7 +42,10 @@ class LockManager(models.Manager):
|
||||
logger.debug('unable to acquire lock: %s', name)
|
||||
raise LockError('Unable to acquire lock')
|
||||
except OperationalError as exception:
|
||||
raise LockError('Operational error while trying to acquire lock: %s; %s', name, exception)
|
||||
raise LockError(
|
||||
'Operational error while trying to acquire lock: %s; %s',
|
||||
name, exception
|
||||
)
|
||||
else:
|
||||
logger.debug('acquired lock: %s', name)
|
||||
return lock
|
||||
|
||||
@@ -10,9 +10,15 @@ from .settings import DEFAULT_LOCK_TIMEOUT
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Lock(models.Model):
|
||||
creation_datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('Creation datetime'))
|
||||
timeout = models.IntegerField(default=DEFAULT_LOCK_TIMEOUT, verbose_name=_('Timeout'))
|
||||
name = models.CharField(max_length=64, unique=True, verbose_name=_('Name'))
|
||||
creation_datetime = models.DateTimeField(
|
||||
auto_now_add=True, verbose_name=_('Creation datetime')
|
||||
)
|
||||
timeout = models.IntegerField(
|
||||
default=DEFAULT_LOCK_TIMEOUT, verbose_name=_('Timeout')
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=64, unique=True, verbose_name=_('Name')
|
||||
)
|
||||
|
||||
objects = LockManager()
|
||||
|
||||
@@ -27,7 +33,9 @@ class Lock(models.Model):
|
||||
|
||||
def release(self):
|
||||
try:
|
||||
lock = Lock.objects.get(name=self.name, creation_datetime=self.creation_datetime)
|
||||
lock = Lock.objects.get(
|
||||
name=self.name, creation_datetime=self.creation_datetime
|
||||
)
|
||||
except Lock.DoesNotExist:
|
||||
# Our lock has expired and was reassigned
|
||||
pass
|
||||
|
||||
@@ -4,4 +4,6 @@ from django.conf import settings
|
||||
|
||||
DEFAULT_LOCK_TIMEOUT_VALUE = 30
|
||||
|
||||
DEFAULT_LOCK_TIMEOUT = getattr(settings, 'LOCK_MANAGER_DEFAULT_LOCK_TIMEOUT', DEFAULT_LOCK_TIMEOUT_VALUE)
|
||||
DEFAULT_LOCK_TIMEOUT = getattr(
|
||||
settings, 'LOCK_MANAGER_DEFAULT_LOCK_TIMEOUT', DEFAULT_LOCK_TIMEOUT_VALUE
|
||||
)
|
||||
|
||||
@@ -14,9 +14,24 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='DocumentMetadata',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('value', models.CharField(db_index=True, max_length=255, null=True, verbose_name='Value', blank=True)),
|
||||
('document', models.ForeignKey(related_name='metadata', verbose_name='Document', to='documents.Document')),
|
||||
(
|
||||
'id', models.AutoField(
|
||||
verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True
|
||||
)
|
||||
),
|
||||
(
|
||||
'value', models.CharField(
|
||||
db_index=True, max_length=255, null=True,
|
||||
verbose_name='Value', blank=True
|
||||
)
|
||||
),
|
||||
(
|
||||
'document', models.ForeignKey(
|
||||
related_name='metadata', verbose_name='Document',
|
||||
to='documents.Document'
|
||||
)
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Document metadata',
|
||||
@@ -27,9 +42,23 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='DocumentTypeMetadataType',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('required', models.BooleanField(default=False, verbose_name='Required')),
|
||||
('document_type', models.ForeignKey(related_name='metadata', verbose_name='Document type', to='documents.DocumentType')),
|
||||
(
|
||||
'id', models.AutoField(
|
||||
verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True
|
||||
)
|
||||
),
|
||||
(
|
||||
'required', models.BooleanField(
|
||||
default=False, verbose_name='Required'
|
||||
)
|
||||
),
|
||||
(
|
||||
'document_type', models.ForeignKey(
|
||||
related_name='metadata',
|
||||
verbose_name='Document type', to='documents.DocumentType'
|
||||
)
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Document type metadata type options',
|
||||
@@ -40,12 +69,47 @@ class Migration(migrations.Migration):
|
||||
migrations.CreateModel(
|
||||
name='MetadataType',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(help_text='Do not use python reserved words, or spaces.', unique=True, max_length=48, verbose_name='Name')),
|
||||
('title', models.CharField(max_length=48, verbose_name='Title')),
|
||||
('default', models.CharField(help_text='Enter a string to be evaluated.', max_length=128, null=True, verbose_name='Default', blank=True)),
|
||||
('lookup', models.TextField(help_text='Enter a string to be evaluated that returns an iterable.', null=True, verbose_name='Lookup', blank=True)),
|
||||
('validation', models.CharField(blank=True, max_length=64, verbose_name='Validation function name', choices=[('Parse date', 'Parse date'), ('Parse date and time', 'Parse date and time'), ('Parse time', 'Parse time')])),
|
||||
(
|
||||
'id', models.AutoField(
|
||||
verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True
|
||||
)
|
||||
),
|
||||
(
|
||||
'name', models.CharField(
|
||||
help_text='Do not use python reserved words, '
|
||||
'or spaces.', unique=True, max_length=48,
|
||||
verbose_name='Name'
|
||||
)
|
||||
),
|
||||
(
|
||||
'title', models.CharField(
|
||||
max_length=48, verbose_name='Title'
|
||||
)
|
||||
),
|
||||
(
|
||||
'default', models.CharField(
|
||||
help_text='Enter a string to be evaluated.',
|
||||
max_length=128, null=True, verbose_name='Default', blank=True
|
||||
)
|
||||
),
|
||||
(
|
||||
'lookup', models.TextField(
|
||||
help_text='Enter a string to be evaluated that '
|
||||
'returns an iterable.', null=True,
|
||||
verbose_name='Lookup', blank=True)
|
||||
),
|
||||
(
|
||||
'validation', models.CharField(
|
||||
blank=True, max_length=64,
|
||||
verbose_name='Validation function name',
|
||||
choices=[
|
||||
('Parse date', 'Parse date'),
|
||||
('Parse date and time', 'Parse date and time'),
|
||||
('Parse time', 'Parse time')
|
||||
]
|
||||
)
|
||||
),
|
||||
],
|
||||
options={
|
||||
'ordering': ('title',),
|
||||
@@ -57,7 +121,9 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='documenttypemetadatatype',
|
||||
name='metadata_type',
|
||||
field=models.ForeignKey(verbose_name='Metadata type', to='metadata.MetadataType'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='Metadata type', to='metadata.MetadataType'
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
@@ -67,7 +133,9 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name='documentmetadata',
|
||||
name='metadata_type',
|
||||
field=models.ForeignKey(verbose_name='Type', to='metadata.MetadataType'),
|
||||
field=models.ForeignKey(
|
||||
verbose_name='Type', to='metadata.MetadataType'
|
||||
),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
|
||||
@@ -13,7 +13,10 @@ class Migration(migrations.Migration):
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='metadatatype',
|
||||
options={'ordering': ('label',), 'verbose_name': 'Metadata type', 'verbose_name_plural': 'Metadata types'},
|
||||
options={
|
||||
'ordering': ('label',), 'verbose_name': 'Metadata type',
|
||||
'verbose_name_plural': 'Metadata types'
|
||||
},
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='metadatatype',
|
||||
|
||||
@@ -11,8 +11,8 @@ from permissions import Permission
|
||||
class MayanObjectPermissionsFilter(BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
required_permission = getattr(
|
||||
view, 'mayan_object_permissions', {}).get(request.method, None
|
||||
)
|
||||
view, 'mayan_object_permissions', {}
|
||||
).get(request.method, None)
|
||||
|
||||
if required_permission:
|
||||
try:
|
||||
|
||||
@@ -13,8 +13,8 @@ from permissions import Permission
|
||||
class MayanPermission(BasePermission):
|
||||
def has_permission(self, request, view):
|
||||
required_permission = getattr(
|
||||
view, 'mayan_view_permissions', {}).get(request.method, None
|
||||
)
|
||||
view, 'mayan_view_permissions', {}
|
||||
).get(request.method, None)
|
||||
|
||||
if required_permission:
|
||||
try:
|
||||
@@ -28,8 +28,8 @@ class MayanPermission(BasePermission):
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
required_permission = getattr(
|
||||
view, 'mayan_object_permissions', {}).get(request.method, None
|
||||
)
|
||||
view, 'mayan_object_permissions', {}
|
||||
).get(request.method, None)
|
||||
|
||||
if required_permission:
|
||||
try:
|
||||
|
||||
@@ -8,7 +8,8 @@ version_0_urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^$', Version_0.as_view(), name='api-version-0'),
|
||||
url(
|
||||
r'^(?P<app_name>\w+)/$', APIAppView.as_view(), name='api-version-0-app'
|
||||
r'^(?P<app_name>\w+)/$', APIAppView.as_view(),
|
||||
name='api-version-0-app'
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -46,7 +46,9 @@ class Version_0(generics.GenericAPIView):
|
||||
{
|
||||
'name': unicode(endpoint),
|
||||
'url': reverse('api-version-0-app',
|
||||
args=[unicode(endpoint)], request=request, format=format)
|
||||
args=[unicode(endpoint)], request=request,
|
||||
format=format
|
||||
)
|
||||
} for endpoint in APIEndPoint.get_all()
|
||||
],
|
||||
})
|
||||
|
||||
@@ -40,7 +40,9 @@ class SmartSettingsApp(MayanAppConfig):
|
||||
)
|
||||
SourceColumn(
|
||||
source=Setting, label=_('Found in path'),
|
||||
attribute=encapsulate(lambda instance: exists_widget(instance.value) if instance.is_path else _('n/a'))
|
||||
attribute=encapsulate(
|
||||
lambda instance: exists_widget(instance.value) if instance.is_path else _('n/a')
|
||||
)
|
||||
)
|
||||
|
||||
menu_object.bind_links(
|
||||
@@ -54,4 +56,6 @@ class SmartSettingsApp(MayanAppConfig):
|
||||
except ImportError:
|
||||
logger.debug('App %s has not settings.py file', app.name)
|
||||
else:
|
||||
logger.debug('Imported settings.py file for app %s', app.name)
|
||||
logger.debug(
|
||||
'Imported settings.py file for app %s', app.name
|
||||
)
|
||||
|
||||
@@ -7,10 +7,11 @@ from .views import NamespaceDetailView, NamespaceListView
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(
|
||||
r'^namespace/all/$', NamespaceListView.as_view(), name='namespace_list'
|
||||
r'^namespace/all/$', NamespaceListView.as_view(),
|
||||
name='namespace_list'
|
||||
),
|
||||
url(
|
||||
r'^namespace/(?P<namespace_name>\w+)/$', NamespaceDetailView.as_view(),
|
||||
name='namespace_detail'
|
||||
r'^namespace/(?P<namespace_name>\w+)/$',
|
||||
NamespaceDetailView.as_view(), name='namespace_detail'
|
||||
),
|
||||
)
|
||||
|
||||
@@ -49,7 +49,11 @@ class SourcesApp(MayanAppConfig):
|
||||
|
||||
MissingItem(
|
||||
label=_('Create a document source'),
|
||||
description=_('Document sources are the way in which new documents are feed to Mayan EDMS, create at least a web form source to be able to upload documents from a browser.'),
|
||||
description=_(
|
||||
'Document sources are the way in which new documents are '
|
||||
'feed to Mayan EDMS, create at least a web form source to '
|
||||
'be able to upload documents from a browser.'
|
||||
),
|
||||
condition=lambda: not Source.objects.exists(),
|
||||
view='sources:setup_source_list'
|
||||
)
|
||||
@@ -131,7 +135,8 @@ class SourcesApp(MayanAppConfig):
|
||||
)
|
||||
|
||||
post_upgrade.connect(
|
||||
initialize_periodic_tasks, dispatch_uid='initialize_periodic_tasks'
|
||||
initialize_periodic_tasks,
|
||||
dispatch_uid='initialize_periodic_tasks'
|
||||
)
|
||||
post_initial_setup.connect(
|
||||
create_default_document_source,
|
||||
|
||||
@@ -51,7 +51,7 @@ class UploadDocumentTestCase(TestCase):
|
||||
self.client.post(
|
||||
reverse(
|
||||
'sources:setup_source_create', args=[SOURCE_CHOICE_WEB_FORM]
|
||||
), {'label': 'test', 'uncompress': 'n', 'enabled': True}
|
||||
), {'label': 'test', 'uncompress': 'n', 'enabled': True}
|
||||
)
|
||||
self.assertEqual(WebFormSource.objects.count(), 1)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user