diff --git a/mayan/apps/acls/apps.py b/mayan/apps/acls/apps.py index edcb5061be..e673c67b48 100644 --- a/mayan/apps/acls/apps.py +++ b/mayan/apps/acls/apps.py @@ -15,5 +15,8 @@ class ACLsApp(MayanAppConfig): def ready(self): super(ACLsApp, self).ready() - menu_object.bind_links(links=[link_acl_permissions, link_acl_delete], sources=[AccessControlList]) + menu_object.bind_links( + links=[link_acl_permissions, link_acl_delete], + sources=[AccessControlList] + ) menu_sidebar.bind_links(links=[link_acl_new], sources=['acls:acl_list']) diff --git a/mayan/apps/acls/links.py b/mayan/apps/acls/links.py index b824662632..b5d847a989 100644 --- a/mayan/apps/acls/links.py +++ b/mayan/apps/acls/links.py @@ -16,7 +16,19 @@ def get_kwargs_factory(variable_name): return get_kwargs -link_acl_delete = Link(permissions=[permission_acl_edit], tags='dangerous', text=_('Delete'), view='acls:acl_delete', args='resolved_object.pk') -link_acl_list = Link(permissions=[permission_acl_view], text=_('ACLs'), view='acls:acl_list', kwargs=get_kwargs_factory('resolved_object')) -link_acl_new = Link(permissions=[permission_acl_edit], text=_('New ACL'), view='acls:acl_new', kwargs=get_kwargs_factory('resolved_object')) -link_acl_permissions = Link(permissions=[permission_acl_edit], text=_('Permissions'), view='acls:acl_permissions', args='resolved_object.pk') +link_acl_delete = Link( + permissions=[permission_acl_edit], tags='dangerous', text=_('Delete'), + view='acls:acl_delete', args='resolved_object.pk' +) +link_acl_list = Link( + permissions=[permission_acl_view], text=_('ACLs'), view='acls:acl_list', + kwargs=get_kwargs_factory('resolved_object') +) +link_acl_new = Link( + permissions=[permission_acl_edit], text=_('New ACL'), + view='acls:acl_new', kwargs=get_kwargs_factory('resolved_object') +) +link_acl_permissions = Link( + permissions=[permission_acl_edit], text=_('Permissions'), + view='acls:acl_permissions', args='resolved_object.pk' +) diff --git a/mayan/apps/acls/managers.py b/mayan/apps/acls/managers.py index dada17515a..706525670f 100644 --- a/mayan/apps/acls/managers.py +++ b/mayan/apps/acls/managers.py @@ -38,7 +38,10 @@ class AccessControlListManager(models.Manager): parent_object = getattr(instance, parent_accessor) content_type = ContentType.objects.get_for_model(parent_object) try: - return self.get(role=role, content_type=content_type, object_id=parent_object.pk).permissions.all() + return self.get( + role=role, content_type=content_type, + object_id=parent_object.pk + ).permissions.all() except self.model.DoesNotExist: return StoredPermission.objects.none() @@ -47,7 +50,9 @@ class AccessControlListManager(models.Manager): return True try: - stored_permissions = [permission.stored_permission for permission in permissions] + stored_permissions = [ + permission.stored_permission for permission in permissions + ] except TypeError: stored_permissions = [permissions.stored_permission] @@ -80,14 +85,22 @@ class AccessControlListManager(models.Manager): instance = queryset.first() if instance: parent_object = getattr(instance, parent_accessor) - parent_content_type = ContentType.objects.get_for_model(parent_object) - parent_queryset = self.filter(content_type=parent_content_type, role__in=user_roles, permissions=permission.stored_permission) + parent_content_type = ContentType.objects.get_for_model( + parent_object + ) + parent_queryset = self.filter( + 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)}) else: parent_acl_query = Q() # Directly granted access content_type = ContentType.objects.get_for_model(queryset.model) - acl_query = Q(pk__in=self.filter(content_type=content_type, role__in=user_roles, permissions=permission.stored_permission).values_list('object_id', flat=True)) + acl_query = Q(pk__in=self.filter( + content_type=content_type, role__in=user_roles, + permissions=permission.stored_permission + ).values_list('object_id', flat=True)) return queryset.filter(parent_acl_query | acl_query) diff --git a/mayan/apps/authentication/apps.py b/mayan/apps/authentication/apps.py index 7280dc6839..e5a337a4c5 100644 --- a/mayan/apps/authentication/apps.py +++ b/mayan/apps/authentication/apps.py @@ -22,5 +22,11 @@ class AuthenticationApp(MayanAppConfig): links=[ link_password_change, link_logout ], - sources=['common:current_user_details', 'common:current_user_edit', 'common:current_user_locale_profile_details', 'common:current_user_locale_profile_edit', 'authentication:password_change_view', 'common:setup_list', 'common:tools_list'] + sources=[ + 'common:current_user_details', 'common:current_user_edit', + 'common:current_user_locale_profile_details', + 'common:current_user_locale_profile_edit', + 'authentication:password_change_view', + 'common:setup_list', 'common:tools_list' + ] ) diff --git a/mayan/apps/authentication/links.py b/mayan/apps/authentication/links.py index eba9531345..dc7385bd9f 100644 --- a/mayan/apps/authentication/links.py +++ b/mayan/apps/authentication/links.py @@ -9,5 +9,10 @@ def has_usable_password(context): return context['request'].user.has_usable_password -link_logout = Link(icon='fa fa-sign-out', text=_('Logout'), view='authentication:logout_view') -link_password_change = Link(condition=has_usable_password, icon='fa fa-key', text=_('Change password'), view='authentication:password_change_view') +link_logout = Link( + icon='fa fa-sign-out', text=_('Logout'), view='authentication:logout_view' +) +link_password_change = Link( + condition=has_usable_password, icon='fa fa-key', text=_('Change password'), + view='authentication:password_change_view' +) diff --git a/mayan/apps/authentication/settings.py b/mayan/apps/authentication/settings.py index 3722c5d842..98a2980da5 100644 --- a/mayan/apps/authentication/settings.py +++ b/mayan/apps/authentication/settings.py @@ -5,4 +5,9 @@ from django.utils.translation import ugettext_lazy as _ from smart_settings import Namespace 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')) +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' + ) +) diff --git a/mayan/apps/authentication/test_views.py b/mayan/apps/authentication/test_views.py index 434cd86524..150f275d6a 100644 --- a/mayan/apps/authentication/test_views.py +++ b/mayan/apps/authentication/test_views.py @@ -19,17 +19,24 @@ class UserLoginTestCase(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() def test_normal_behaviour(self): setting_login_method.value = 'username' response = self.client.get(reverse('documents:document_list')) - self.assertRedirects(response, 'http://testserver/authentication/login/') + self.assertRedirects( + response, 'http://testserver/authentication/login/' + ) def test_username_login(self): setting_login_method.value = 'username' - 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) response = self.client.get(reverse('documents:document_list')) # We didn't get redirected to the login URL @@ -39,10 +46,14 @@ class UserLoginTestCase(TestCase): with self.settings(AUTHENTICATION_BACKENDS=('authentication.auth.email_auth_backend.EmailAuthBackend',)): setting_login_method.value = 'email' - 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.assertFalse(logged_in) - logged_in = self.client.login(email=TEST_ADMIN_EMAIL, password=TEST_ADMIN_PASSWORD) + logged_in = self.client.login( + email=TEST_ADMIN_EMAIL, password=TEST_ADMIN_PASSWORD + ) self.assertTrue(logged_in) response = self.client.get(reverse('documents:document_list')) @@ -52,9 +63,16 @@ class UserLoginTestCase(TestCase): def test_username_login_via_views(self): setting_login_method.value = 'username' response = self.client.get(reverse('documents:document_list')) - self.assertRedirects(response, 'http://testserver/authentication/login/') + self.assertRedirects( + response, 'http://testserver/authentication/login/' + ) - response = self.client.post(reverse(settings.LOGIN_URL), {'username': TEST_ADMIN_USERNAME, 'password': TEST_ADMIN_PASSWORD}) + response = self.client.post( + reverse(settings.LOGIN_URL), { + 'username': TEST_ADMIN_USERNAME, + 'password': TEST_ADMIN_PASSWORD + } + ) response = self.client.get(reverse('documents:document_list')) # We didn't get redirected to the login URL self.assertEqual(response.status_code, 200) @@ -63,9 +81,15 @@ class UserLoginTestCase(TestCase): with self.settings(AUTHENTICATION_BACKENDS=('authentication.auth.email_auth_backend.EmailAuthBackend',)): setting_login_method.value = 'email' response = self.client.get(reverse('documents:document_list')) - self.assertRedirects(response, 'http://testserver/authentication/login/') + self.assertRedirects( + response, 'http://testserver/authentication/login/' + ) - response = self.client.post(reverse(settings.LOGIN_URL), {'email': TEST_ADMIN_EMAIL, 'password': TEST_ADMIN_PASSWORD}, follow=True) + response = self.client.post( + reverse(settings.LOGIN_URL), { + 'email': TEST_ADMIN_EMAIL, 'password': TEST_ADMIN_PASSWORD + }, follow=True + ) self.assertEqual(response.status_code, 200) response = self.client.get(reverse('documents:document_list')) diff --git a/mayan/apps/authentication/urls.py b/mayan/apps/authentication/urls.py index 268dac3439..dfeaaaa05a 100644 --- a/mayan/apps/authentication/urls.py +++ b/mayan/apps/authentication/urls.py @@ -5,16 +5,46 @@ from django.conf.urls import patterns, url urlpatterns = patterns( 'authentication.views', - url(r'^login/$', 'login_view', (), name='login_view'), - url(r'^password/change/done/$', 'password_change_done', (), name='password_change_done'), - url(r'^password/change/$', 'password_change_view', (), name='password_change_view'), + url(r'^login/$', 'login_view', name='login_view'), + url( + r'^password/change/done/$', 'password_change_done', + name='password_change_done' + ), + url( + r'^password/change/$', 'password_change_view', + name='password_change_view' + ), ) urlpatterns += patterns( '', - url(r'^logout/$', 'django.contrib.auth.views.logout', {'next_page': settings.LOGIN_REDIRECT_URL}, name='logout_view'), - url(r'^password/reset/$', 'django.contrib.auth.views.password_reset', {'email_template_name': 'appearance/password_reset_email.html', 'template_name': 'appearance/password_reset_form.html', 'post_reset_redirect': '/password/reset/done'}, name='password_reset_view'), - url(r'^password/reset/confirm/(?P[0-9A-Za-z]+)-(?P.+)/$', 'django.contrib.auth.views.password_reset_confirm', {'template_name': 'appearance/password_reset_confirm.html', 'post_reset_redirect': '/password/reset/complete/'}, name='password_reset_confirm_view'), - url(r'^password/reset/complete/$', 'django.contrib.auth.views.password_reset_complete', {'template_name': 'appearance/password_reset_complete.html'}, name='password_reset_complete_view'), - url(r'^password/reset/done/$', 'django.contrib.auth.views.password_reset_done', {'template_name': 'appearance/password_reset_done.html'}, name='password_reset_done_view'), + url( + r'^logout/$', 'django.contrib.auth.views.logout', + {'next_page': settings.LOGIN_REDIRECT_URL}, name='logout_view' + ), + url( + r'^password/reset/$', 'django.contrib.auth.views.password_reset', + { + 'email_template_name': 'appearance/password_reset_email.html', + 'template_name': 'appearance/password_reset_form.html', + 'post_reset_redirect': '/password/reset/done' + }, name='password_reset_view' + ), + url( + r'^password/reset/confirm/(?P[0-9A-Za-z]+)-(?P.+)/$', + 'django.contrib.auth.views.password_reset_confirm', { + 'template_name': 'appearance/password_reset_confirm.html', + 'post_reset_redirect': '/password/reset/complete/' + }, name='password_reset_confirm_view' + ), + url( + r'^password/reset/complete/$', + 'django.contrib.auth.views.password_reset_complete', { + 'template_name': 'appearance/password_reset_complete.html' + }, name='password_reset_complete_view'), + url( + r'^password/reset/done/$', + 'django.contrib.auth.views.password_reset_done', { + 'template_name': 'appearance/password_reset_done.html' + }, name='password_reset_done_view'), ) diff --git a/mayan/apps/authentication/views.py b/mayan/apps/authentication/views.py index 6a5ca0ffd4..e0316f1a8b 100644 --- a/mayan/apps/authentication/views.py +++ b/mayan/apps/authentication/views.py @@ -48,5 +48,7 @@ def password_change_done(request): View called when the new user password has been accepted """ - messages.success(request, _('Your password has been successfully changed.')) + messages.success( + request, _('Your password has been successfully changed.') + ) return redirect('common:current_user_details') diff --git a/mayan/apps/checkouts/apps.py b/mayan/apps/checkouts/apps.py index 56edc48152..6e576c4245 100644 --- a/mayan/apps/checkouts/apps.py +++ b/mayan/apps/checkouts/apps.py @@ -37,10 +37,22 @@ class CheckoutsApp(MayanAppConfig): APIEndPoint('checkouts') - Document.add_to_class('check_in', lambda document, user=None: DocumentCheckout.objects.check_in_document(document, user)) - Document.add_to_class('checkout_info', lambda document: DocumentCheckout.objects.document_checkout_info(document)) - Document.add_to_class('checkout_state', lambda document: DocumentCheckout.objects.document_checkout_state(document)) - Document.add_to_class('is_checked_out', lambda document: DocumentCheckout.objects.is_document_checked_out(document)) + Document.add_to_class( + 'check_in', + lambda document, user=None: DocumentCheckout.objects.check_in_document(document, user) + ) + Document.add_to_class( + 'checkout_info', + lambda document: DocumentCheckout.objects.document_checkout_info(document) + ) + Document.add_to_class( + 'checkout_state', + lambda document: DocumentCheckout.objects.document_checkout_state(document) + ) + Document.add_to_class( + 'is_checked_out', + lambda document: DocumentCheckout.objects.is_document_checked_out(document) + ) ModelPermission.register( model=Document, permissions=( @@ -54,13 +66,18 @@ class CheckoutsApp(MayanAppConfig): { 'task_check_expired_check_outs': { 'task': 'checkouts.tasks.task_check_expired_check_outs', - 'schedule': timedelta(seconds=CHECK_EXPIRED_CHECK_OUTS_INTERVAL), + 'schedule': timedelta( + seconds=CHECK_EXPIRED_CHECK_OUTS_INTERVAL + ), }, } ) app.conf.CELERY_QUEUES.append( - Queue('checkouts_periodic', Exchange('checkouts_periodic'), routing_key='checkouts_periodic', delivery_mode=1), + Queue( + 'checkouts_periodic', Exchange('checkouts_periodic'), + routing_key='checkouts_periodic', delivery_mode=1 + ), ) app.conf.CELERY_ROUTES.update( @@ -73,6 +90,15 @@ class CheckoutsApp(MayanAppConfig): menu_facet.bind_links(links=[link_checkout_info], sources=[Document]) menu_main.bind_links(links=[link_checkout_list]) - menu_sidebar.bind_links(links=[link_checkout_document, link_checkin_document], sources=['checkouts:checkout_info', 'checkouts:checkout_document', 'checkouts:checkin_document']) + menu_sidebar.bind_links( + links=[link_checkout_document, link_checkin_document], + sources=[ + 'checkouts:checkout_info', 'checkouts:checkout_document', + 'checkouts:checkin_document' + ] + ) - pre_save.connect(check_if_new_versions_allowed, dispatch_uid='document_index_delete', sender=DocumentVersion) + pre_save.connect( + check_if_new_versions_allowed, + dispatch_uid='document_index_delete', sender=DocumentVersion + ) diff --git a/mayan/apps/checkouts/events.py b/mayan/apps/checkouts/events.py index 28994ba725..854759ada8 100644 --- a/mayan/apps/checkouts/events.py +++ b/mayan/apps/checkouts/events.py @@ -4,7 +4,17 @@ from django.utils.translation import ugettext_lazy as _ from events.classes import Event -event_document_auto_check_in = Event(name='checkouts_document_auto_check_in', label=_('Document automatically checked in')) -event_document_check_in = Event(name='checkouts_document_check_in', label=_('Document checked in')) -event_document_check_out = Event(name='checkouts_document_check_out', label=_('Document checked out')) -event_document_forceful_check_in = Event(name='checkouts_document_forceful_check_in', label=_('Document forcefully checked in')) +event_document_auto_check_in = Event( + name='checkouts_document_auto_check_in', + label=_('Document automatically checked in') +) +event_document_check_in = Event( + name='checkouts_document_check_in', label=_('Document checked in') +) +event_document_check_out = Event( + name='checkouts_document_check_out', label=_('Document checked out') +) +event_document_forceful_check_in = Event( + name='checkouts_document_forceful_check_in', + label=_('Document forcefully checked in') +) diff --git a/mayan/apps/checkouts/handlers.py b/mayan/apps/checkouts/handlers.py index 0f84307a8c..a339365af9 100644 --- a/mayan/apps/checkouts/handlers.py +++ b/mayan/apps/checkouts/handlers.py @@ -8,4 +8,8 @@ from .models import DocumentCheckout def check_if_new_versions_allowed(sender, **kwargs): if not DocumentCheckout.objects.are_document_new_versions_allowed(kwargs['instance'].document): - raise NewDocumentVersionNotAllowed(_('New versions not allowed for the checkedout document: %s' % kwargs['instance'].document)) + raise NewDocumentVersionNotAllowed( + _( + 'New versions not allowed for the checkedout document: %s' % kwargs['instance'].document + ) + ) diff --git a/mayan/apps/checkouts/links.py b/mayan/apps/checkouts/links.py index c6e9fb7ae3..223f118665 100644 --- a/mayan/apps/checkouts/links.py +++ b/mayan/apps/checkouts/links.py @@ -18,7 +18,24 @@ def is_not_checked_out(context): return not context['object'].is_checked_out() -link_checkout_list = Link(icon='fa fa-shopping-cart', text=_('Checkouts'), view='checkouts:checkout_list') -link_checkout_document = Link(condition=is_not_checked_out, permissions=[permission_document_checkout], text=_('Check out document'), view='checkouts:checkout_document', args='object.pk') -link_checkin_document = Link(condition=is_checked_out, permissions=[permission_document_checkin, permission_document_checkin_override], text=_('Check in document'), view='checkouts:checkin_document', args='object.pk') -link_checkout_info = Link(permissions=[permission_document_checkin, permission_document_checkin_override, permission_document_checkout], text=_('Check in/out'), view='checkouts:checkout_info', args='object.pk') +link_checkout_list = Link( + icon='fa fa-shopping-cart', text=_('Checkouts'), + view='checkouts:checkout_list' +) +link_checkout_document = Link( + condition=is_not_checked_out, permissions=[permission_document_checkout], + text=_('Check out document'), view='checkouts:checkout_document', + args='object.pk' +) +link_checkin_document = Link( + condition=is_checked_out, permissions=[ + permission_document_checkin, permission_document_checkin_override + ], text=_('Check in document'), view='checkouts:checkin_document', + args='object.pk' +) +link_checkout_info = Link( + permissions=[ + permission_document_checkin, permission_document_checkin_override, + permission_document_checkout + ], text=_('Check in/out'), view='checkouts:checkout_info', args='object.pk' +) diff --git a/mayan/apps/checkouts/managers.py b/mayan/apps/checkouts/managers.py index ca3211a2a9..5af640227f 100644 --- a/mayan/apps/checkouts/managers.py +++ b/mayan/apps/checkouts/managers.py @@ -19,13 +19,24 @@ logger = logging.getLogger(__name__) class DocumentCheckoutManager(models.Manager): def checkout_document(self, document, expiration_datetime, user, block_new_version=True): - self.create(document=document, expiration_datetime=expiration_datetime, user=user, block_new_version=block_new_version) + self.create( + document=document, expiration_datetime=expiration_datetime, + user=user, block_new_version=block_new_version + ) def checked_out_documents(self): - return Document.objects.filter(pk__in=self.model.objects.all().values_list('document__pk', flat=True)) + return Document.objects.filter( + pk__in=self.model.objects.all().values_list( + 'document__pk', flat=True + ) + ) def expired_check_outs(self): - expired_list = Document.objects.filter(pk__in=self.model.objects.filter(expiration_datetime__lte=now()).values_list('document__pk', flat=True)) + expired_list = Document.objects.filter( + pk__in=self.model.objects.filter( + expiration_datetime__lte=now() + ).values_list('document__pk', flat=True) + ) logger.debug('expired_list: %s', expired_list) return expired_list @@ -47,7 +58,9 @@ class DocumentCheckoutManager(models.Manager): else: if user: if self.document_checkout_info(document).user != user: - event_document_forceful_check_in.commit(actor=user, target=document) + event_document_forceful_check_in.commit( + actor=user, target=document + ) else: event_document_check_in.commit(actor=user, target=document) else: diff --git a/mayan/apps/checkouts/models.py b/mayan/apps/checkouts/models.py index 2ead7b9834..2aa28c7d79 100644 --- a/mayan/apps/checkouts/models.py +++ b/mayan/apps/checkouts/models.py @@ -24,11 +24,26 @@ class DocumentCheckout(models.Model): """ Model to store the state and information of a document checkout """ - document = models.ForeignKey(Document, unique=True, verbose_name=_('Document')) - checkout_datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('Check out date and time')) - expiration_datetime = models.DateTimeField(help_text=_('Amount of time to hold the document checked out in minutes.'), verbose_name=_('Check out expiration date and time')) + document = models.ForeignKey( + Document, unique=True, verbose_name=_('Document') + ) + checkout_datetime = models.DateTimeField( + auto_now_add=True, verbose_name=_('Check out date and time') + ) + expiration_datetime = models.DateTimeField( + help_text=_( + 'Amount of time to hold the document checked out in minutes.' + ), + verbose_name=_('Check out expiration date and time') + ) user = models.ForeignKey(settings.AUTH_USER_MODEL, verbose_name=_('User')) - block_new_version = models.BooleanField(default=True, help_text=_('Do not allow new version of this document to be uploaded.'), verbose_name=_('Block new version upload')) + block_new_version = models.BooleanField( + default=True, + help_text=_( + 'Do not allow new version of this document to be uploaded.' + ), + verbose_name=_('Block new version upload') + ) objects = DocumentCheckoutManager() @@ -40,7 +55,9 @@ class DocumentCheckout(models.Model): def clean(self): if self.expiration_datetime < now(): - raise ValidationError(_('Check out expiration date and time must be in the future.')) + raise ValidationError( + _('Check out expiration date and time must be in the future.') + ) def save(self, *args, **kwargs): new_checkout = not self.pk @@ -49,8 +66,13 @@ class DocumentCheckout(models.Model): result = super(DocumentCheckout, self).save(*args, **kwargs) if new_checkout: - event_document_check_out.commit(actor=self.user, target=self.document) - logger.info('Document "%s" checked out by user "%s"', self.document, self.user) + event_document_check_out.commit( + actor=self.user, target=self.document + ) + logger.info( + 'Document "%s" checked out by user "%s"', + self.document, self.user + ) return result diff --git a/mayan/apps/checkouts/permissions.py b/mayan/apps/checkouts/permissions.py index 5e20291464..b555d7e9c4 100644 --- a/mayan/apps/checkouts/permissions.py +++ b/mayan/apps/checkouts/permissions.py @@ -6,6 +6,12 @@ from permissions import PermissionNamespace namespace = PermissionNamespace('checkouts', _('Document checkout')) -permission_document_checkin = namespace.add_permission(name='checkin_document', label=_('Check in documents')) -permission_document_checkin_override = namespace.add_permission(name='checkin_document_override', label=_('Forcefully check in documents')) -permission_document_checkout = namespace.add_permission(name='checkout_document', label=_('Check out documents')) +permission_document_checkin = namespace.add_permission( + name='checkin_document', label=_('Check in documents') +) +permission_document_checkin_override = namespace.add_permission( + name='checkin_document_override', label=_('Forcefully check in documents') +) +permission_document_checkout = namespace.add_permission( + name='checkout_document', label=_('Check out documents') +) diff --git a/mayan/apps/checkouts/tasks.py b/mayan/apps/checkouts/tasks.py index 6319a3d261..5b4327d0e7 100644 --- a/mayan/apps/checkouts/tasks.py +++ b/mayan/apps/checkouts/tasks.py @@ -17,10 +17,11 @@ def task_check_expired_check_outs(): lock_id = 'task_expired_check_outs' try: logger.debug('trying to acquire lock: %s', lock_id) - lock = Lock.acquire_lock(name=lock_id, timeout=CHECKOUT_EXPIRATION_LOCK_EXPIRE) + lock = Lock.acquire_lock( + name=lock_id, timeout=CHECKOUT_EXPIRATION_LOCK_EXPIRE + ) logger.debug('acquired lock: %s', lock_id) DocumentCheckout.objects.check_in_expired_check_outs() lock.release() except LockError: logger.debug('unable to obtain lock') - pass diff --git a/mayan/apps/checkouts/urls.py b/mayan/apps/checkouts/urls.py index 6597f2eb4f..845e0f2196 100644 --- a/mayan/apps/checkouts/urls.py +++ b/mayan/apps/checkouts/urls.py @@ -8,13 +8,28 @@ from .views import CheckoutDocumentView, CheckoutListView urlpatterns = patterns( 'checkouts.views', url(r'^list/$', CheckoutListView.as_view(), name='checkout_list'), - url(r'^(?P\d+)/check/out/$', CheckoutDocumentView.as_view(), name='checkout_document'), - url(r'^(?P\d+)/check/in/$', 'checkin_document', name='checkin_document'), - url(r'^(?P\d+)/check/info/$', 'checkout_info', name='checkout_info'), + url( + r'^(?P\d+)/check/out/$', CheckoutDocumentView.as_view(), + name='checkout_document' + ), + url( + r'^(?P\d+)/check/in/$', 'checkin_document', + name='checkin_document' + ), + url( + r'^(?P\d+)/check/info/$', 'checkout_info', + name='checkout_info' + ), ) api_urls = patterns( '', - url(r'^documents/$', APICheckedoutDocumentListView.as_view(), name='checkout-document-list'), - url(r'^documents/(?P[0-9]+)/$', APICheckedoutDocumentView.as_view(), name='checkedout-document-view'), + url( + r'^documents/$', APICheckedoutDocumentListView.as_view(), + name='checkout-document-list' + ), + url( + r'^documents/(?P[0-9]+)/$', APICheckedoutDocumentView.as_view(), + name='checkedout-document-view' + ), ) diff --git a/mayan/apps/checkouts/views.py b/mayan/apps/checkouts/views.py index f3d1f9583b..2d4f7e4c75 100644 --- a/mayan/apps/checkouts/views.py +++ b/mayan/apps/checkouts/views.py @@ -34,9 +34,13 @@ class CheckoutDocumentView(SingleObjectCreateView): self.document = get_object_or_404(Document, pk=self.kwargs['pk']) try: - Permission.check_permissions(request.user, [permission_document_checkout]) + Permission.check_permissions( + request.user, [permission_document_checkout] + ) except PermissionDenied: - AccessControlList.objects.check_access(permission_document_checkout, request.user, self.document) + AccessControlList.objects.check_access( + permission_document_checkout, request.user, self.document + ) return super(CheckoutDocumentView, self).dispatch(request, *args, **kwargs) @@ -49,9 +53,15 @@ class CheckoutDocumentView(SingleObjectCreateView): except DocumentAlreadyCheckedOut: messages.error(self.request, _('Document already checked out.')) except Exception as exception: - messages.error(self.request, _('Error trying to check out document; %s') % exception) + messages.error( + self.request, + _('Error trying to check out document; %s') % exception + ) else: - messages.success(self.request, _('Document "%s" checked out successfully.') % self.document) + messages.success( + self.request, + _('Document "%s" checked out successfully.') % self.document + ) return HttpResponseRedirect(self.get_success_url()) @@ -73,9 +83,24 @@ class CheckoutListView(DocumentListView): 'title': _('Documents checked out'), 'hide_links': True, 'extra_columns': [ - {'name': _('User'), 'attribute': encapsulate(lambda document: document.checkout_info().user.get_full_name() or document.checkout_info().user)}, - {'name': _('Checkout time and date'), 'attribute': encapsulate(lambda document: document.checkout_info().checkout_datetime)}, - {'name': _('Checkout expiration'), 'attribute': encapsulate(lambda document: document.checkout_info().expiration_datetime)}, + { + 'name': _('User'), + 'attribute': encapsulate( + lambda document: document.checkout_info().user.get_full_name() or document.checkout_info().user + ) + }, + { + 'name': _('Checkout time and date'), + 'attribute': encapsulate( + lambda document: document.checkout_info().checkout_datetime + ) + }, + { + 'name': _('Checkout expiration'), + 'attribute': encapsulate( + lambda document: document.checkout_info().expiration_datetime + ) + }, ], } @@ -83,29 +108,51 @@ class CheckoutListView(DocumentListView): def checkout_info(request, document_pk): document = get_object_or_404(Document, pk=document_pk) try: - Permission.check_permissions(request.user, [permission_document_checkout, permission_document_checkin]) + Permission.check_permissions( + request.user, [ + permission_document_checkout, permission_document_checkin + ] + ) except PermissionDenied: - AccessControlList.objects.check_access([permission_document_checkout, permission_document_checkin], request.user, document) + AccessControlList.objects.check_access( + [permission_document_checkout, permission_document_checkin], + request.user, document + ) - paragraphs = [_('Document status: %s') % STATE_LABELS[document.checkout_state()]] + paragraphs = [ + _('Document status: %s') % STATE_LABELS[document.checkout_state()] + ] if document.is_checked_out(): checkout_info = document.checkout_info() - paragraphs.append(_('User: %s') % (checkout_info.user.get_full_name() or checkout_info.user)) - paragraphs.append(_('Check out time: %s') % checkout_info.checkout_datetime) - paragraphs.append(_('Check out expiration: %s') % checkout_info.expiration_datetime) - paragraphs.append(_('New versions allowed: %s') % (_('Yes') if not checkout_info.block_new_version else _('No'))) + paragraphs.append( + _('User: %s') % (checkout_info.user.get_full_name() or checkout_info.user) + ) + paragraphs.append( + _('Check out time: %s') % checkout_info.checkout_datetime + ) + paragraphs.append( + _('Check out expiration: %s') % checkout_info.expiration_datetime + ) + paragraphs.append( + _('New versions allowed: %s') % (_('Yes') if not checkout_info.block_new_version else _('No')) + ) - return render_to_response('appearance/generic_template.html', { - 'paragraphs': paragraphs, - 'object': document, - 'title': _('Check out details for document: %s') % document - }, context_instance=RequestContext(request)) + return render_to_response( + 'appearance/generic_template.html', { + 'paragraphs': paragraphs, + 'object': document, + 'title': _('Check out details for document: %s') % document + }, + context_instance=RequestContext(request) + ) def checkin_document(request, document_pk): document = get_object_or_404(Document, pk=document_pk) - post_action_redirect = reverse('checkouts:checkout_info', args=[document.pk]) + post_action_redirect = reverse( + 'checkouts:checkout_info', args=[document.pk] + ) 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)))) diff --git a/mayan/apps/checkouts/widgets.py b/mayan/apps/checkouts/widgets.py index d34b2f0f2a..131d03fe91 100644 --- a/mayan/apps/checkouts/widgets.py +++ b/mayan/apps/checkouts/widgets.py @@ -16,8 +16,15 @@ class SplitTimeDeltaWidget(forms.widgets.MultiWidget): def __init__(self, attrs=None): widgets = ( - forms.widgets.NumberInput(attrs={'maxlength': 4, 'style': 'width: 8em;', 'placeholder': _('Period')}), - forms.widgets.Select(attrs={'style': 'width: 8em;'}, choices=TIME_DELTA_UNIT_CHOICES), + forms.widgets.NumberInput( + attrs={ + 'maxlength': 4, 'style': 'width: 8em;', + 'placeholder': _('Period') + } + ), + forms.widgets.Select( + attrs={'style': 'width: 8em;'}, choices=TIME_DELTA_UNIT_CHOICES + ), ) super(SplitTimeDeltaWidget, self).__init__(widgets, attrs) diff --git a/mayan/apps/common/apps.py b/mayan/apps/common/apps.py index 9d6229c931..7f9f9149d4 100644 --- a/mayan/apps/common/apps.py +++ b/mayan/apps/common/apps.py @@ -40,7 +40,13 @@ class MayanAppConfig(apps.AppConfig): else: top_url = '{}/'.format(self.name) - urlpatterns += url(r'^{}'.format(top_url), include('{}.urls'.format(self.name), namespace=self.app_namespace or self.name)), + urlpatterns += url( + r'^{}'.format(top_url), + include( + '{}.urls'.format(self.name), + namespace=self.app_namespace or self.name + ) + ), class CommonApp(MayanAppConfig): @@ -60,7 +66,19 @@ class CommonApp(MayanAppConfig): app.conf.CELERY_DEFAULT_QUEUE = 'default' - menu_facet.bind_links(links=[link_current_user_details, link_current_user_locale_profile_details, link_tools, link_setup], sources=['common:current_user_details', 'common:current_user_edit', 'common:current_user_locale_profile_details', 'common:current_user_locale_profile_edit', 'authentication:password_change_view', 'common:setup_list', 'common:tools_list']) + menu_facet.bind_links( + links=[ + link_current_user_details, + link_current_user_locale_profile_details, link_tools, + link_setup + ], sources=[ + 'common:current_user_details', 'common:current_user_edit', + 'common:current_user_locale_profile_details', + 'common:current_user_locale_profile_edit', + 'authentication:password_change_view', 'common:setup_list', + 'common:tools_list' + ] + ) menu_main.bind_links(links=[link_about], position=-1) menu_secondary.bind_links( links=[link_about, link_license], @@ -70,7 +88,20 @@ class CommonApp(MayanAppConfig): links=[ link_current_user_edit, link_current_user_locale_profile_edit ], - sources=['common:current_user_details', 'common:current_user_edit', 'common:current_user_locale_profile_details', 'common:current_user_locale_profile_edit', 'authentication:password_change_view', 'common:setup_list', 'common:tools_list'] + sources=[ + 'common:current_user_details', 'common:current_user_edit', + 'common:current_user_locale_profile_details', + 'common:current_user_locale_profile_edit', + 'authentication:password_change_view', 'common:setup_list', + 'common:tools_list' + ] + ) + user_logged_in.connect( + user_locale_profile_session_config, + dispatch_uid='user_locale_profile_session_config' + ) + post_save.connect( + user_locale_profile_create, + dispatch_uid='user_locale_profile_create', + sender=settings.AUTH_USER_MODEL ) - user_logged_in.connect(user_locale_profile_session_config, dispatch_uid='user_locale_profile_session_config') - post_save.connect(user_locale_profile_create, dispatch_uid='user_locale_profile_create', sender=settings.AUTH_USER_MODEL) diff --git a/mayan/apps/common/classes.py b/mayan/apps/common/classes.py index 17c6fec4a5..65740911a7 100644 --- a/mayan/apps/common/classes.py +++ b/mayan/apps/common/classes.py @@ -29,7 +29,9 @@ class ModelAttribute(object): @classmethod def get_choices_for(cls, model, type_names=None): - return [(attribute.name, attribute) for attribute in cls.get_for(model, type_names)] + return [ + (attribute.name, attribute) for attribute in cls.get_for(model, type_names) + ] @classmethod def help_text_for(cls, model, type_names=None): @@ -41,7 +43,9 @@ class ModelAttribute(object): def get_display(self, show_name=False): if self.description: - return '{} - {}'.format(self.name if show_name else self.label, self.description) + return '{} - {}'.format( + self.name if show_name else self.label, self.description + ) else: return unicode(self.name if show_name else self.label) diff --git a/mayan/apps/common/compressed_files.py b/mayan/apps/common/compressed_files.py index c1542c8194..156da1e445 100644 --- a/mayan/apps/common/compressed_files.py +++ b/mayan/apps/common/compressed_files.py @@ -63,12 +63,16 @@ class CompressedFile(object): file_input.seek(0) except AttributeError: # If not, keep it - self.zf.write(file_input, arcname=arcname, compress_type=COMPRESSION) + self.zf.write( + file_input, arcname=arcname, compress_type=COMPRESSION + ) else: self.zf.writestr(arcname, file_input.read()) def contents(self): - return [filename for filename in self.zf.namelist() if not filename.endswith('/')] + return [ + filename for filename in self.zf.namelist() if not filename.endswith('/') + ] def get_content(self, filename): return self.zf.read(filename) @@ -93,8 +97,12 @@ class CompressedFile(object): try: # Try for a ZIP file zfobj = zipfile.ZipFile(self.file_object) - filenames = [filename for filename in zfobj.namelist() if not filename.endswith('/')] - return (SimpleUploadedFile(name=filename, content=zfobj.read(filename)) for filename in filenames) + filenames = [ + filename for filename in zfobj.namelist() if not filename.endswith('/') + ] + return ( + SimpleUploadedFile(name=filename, content=zfobj.read(filename)) for filename in filenames + ) except zipfile.BadZipfile: raise NotACompressedFile diff --git a/mayan/apps/common/forms.py b/mayan/apps/common/forms.py index c0add795cf..a18a68736f 100644 --- a/mayan/apps/common/forms.py +++ b/mayan/apps/common/forms.py @@ -72,7 +72,9 @@ class ChoiceForm(forms.Form): self.fields['selection'].label = label self.fields['selection'].help_text = help_text self.fields['selection'].widget.disabled_choices = disabled_choices - self.fields['selection'].widget.attrs.update({'size': 14, 'class': 'choice_form'}) + self.fields['selection'].widget.attrs.update( + {'size': 14, 'class': 'choice_form'} + ) selection = forms.MultipleChoiceField(widget=DisableableSelectWidget()) @@ -84,7 +86,10 @@ class UserForm_view(DetailForm): class Meta: model = User - fields = ('username', 'first_name', 'last_name', 'email', 'is_staff', 'is_superuser', 'last_login', 'date_joined', 'groups') + fields = ( + 'username', 'first_name', 'last_name', 'email', 'is_staff', + 'is_superuser', 'last_login', 'date_joined', 'groups' + ) class UserForm(forms.ModelForm): @@ -119,7 +124,9 @@ class FileDisplayForm(forms.Form): def __init__(self, *args, **kwargs): super(FileDisplayForm, self).__init__(*args, **kwargs) - changelog_path = os.path.join(settings.BASE_DIR, os.sep.join(self.DIRECTORY), self.FILENAME) + changelog_path = os.path.join( + settings.BASE_DIR, os.sep.join(self.DIRECTORY), self.FILENAME + ) fd = open(changelog_path) self.fields['text'].initial = fd.read() fd.close() diff --git a/mayan/apps/common/generics.py b/mayan/apps/common/generics.py index 20bbfdd8a4..0121323835 100644 --- a/mayan/apps/common/generics.py +++ b/mayan/apps/common/generics.py @@ -79,8 +79,14 @@ class AssignRemoveView(ExtraContextMixin, ViewPermissionCheckMixin, ObjectPermis return self.right_list_help_text def get(self, request, *args, **kwargs): - self.unselected_list = ChoiceForm(prefix=self.LEFT_LIST_NAME, choices=self.left_list()) - self.selected_list = ChoiceForm(prefix=self.RIGHT_LIST_NAME, choices=self.right_list(), disabled_choices=self.get_disabled_choices(), help_text=self.get_right_list_help_text()) + self.unselected_list = ChoiceForm( + prefix=self.LEFT_LIST_NAME, choices=self.left_list() + ) + self.selected_list = ChoiceForm( + prefix=self.RIGHT_LIST_NAME, choices=self.right_list(), + disabled_choices=self.get_disabled_choices(), + help_text=self.get_right_list_help_text() + ) return self.render_to_response(self.get_context_data()) def process_form(self, prefix, items_function, action_function): @@ -112,11 +118,20 @@ class AssignRemoveView(ExtraContextMixin, ViewPermissionCheckMixin, ObjectPermis if settings.DEBUG: raise else: - messages.error(self.request, _('Unable to transfer selection: %s.') % label) + messages.error( + self.request, + _('Unable to transfer selection: %s.') % label + ) def post(self, request, *args, **kwargs): - self.process_form(prefix=self.LEFT_LIST_NAME, items_function=self.left_list, action_function=self.add) - self.process_form(prefix=self.RIGHT_LIST_NAME, items_function=self.right_list, action_function=self.remove) + self.process_form( + prefix=self.LEFT_LIST_NAME, items_function=self.left_list, + action_function=self.add + ) + self.process_form( + prefix=self.RIGHT_LIST_NAME, items_function=self.right_list, + action_function=self.remove + ) return self.get(request, *args, **kwargs) def get_context_data(self, **kwargs): @@ -183,7 +198,11 @@ class MultiFormView(FormView): return form def get_forms(self, form_classes): - return dict([(key, self._create_form(key, klass)) for key, klass in form_classes.items()]) + return dict( + [ + (key, self._create_form(key, klass)) for key, klass in form_classes.items() + ] + ) def get_initial(self, form_name): initial_method = 'get_%s_initial' % form_name @@ -246,8 +265,13 @@ class ParentChildListView(ViewPermissionCheckMixin, ObjectPermissionCheckMixin, else: is_empty = len(self.object_list) == 0 if is_empty: - raise Http404(_("Empty list and '%(class_name)s.allow_empty' is False.") - % {'class_name': self.__class__.__name__}) + raise Http404( + _( + "Empty list and '%(class_name)s.allow_empty' is False." + ) % { + 'class_name': self.__class__.__name__ + } + ) context = self.get_context_data(object=self.object) return self.render_to_response(context) @@ -313,7 +337,10 @@ class SingleObjectCreateView(ViewPermissionCheckMixin, ExtraContextMixin, Redire result = super(SingleObjectCreateView, self).form_invalid(form) try: - messages.error(self.request, _('Error creating new %s.') % self.extra_context['object_name']) + messages.error( + self.request, + _('Error creating new %s.') % self.extra_context['object_name'] + ) except KeyError: messages.error(self.request, _('Error creating object.')) @@ -322,9 +349,14 @@ class SingleObjectCreateView(ViewPermissionCheckMixin, ExtraContextMixin, Redire def form_valid(self, form): result = super(SingleObjectCreateView, self).form_valid(form) try: - messages.success(self.request, _('%s created successfully.') % self.extra_context['object_name'].capitalize()) + messages.success( + self.request, + _('%s created successfully.') % self.extra_context['object_name'].capitalize() + ) except KeyError: - messages.success(self.request, _('New object created successfully.')) + messages.success( + self.request, _('New object created successfully.') + ) return result @@ -342,16 +374,24 @@ class SingleObjectDeleteView(ViewPermissionCheckMixin, ObjectPermissionCheckMixi result = super(SingleObjectDeleteView, self).delete(request, *args, **kwargs) except Exception as exception: try: - messages.error(self.request, _('Error deleting %s.') % self.extra_context['object_name']) + messages.error( + self.request, _('Error deleting %s.') % self.extra_context['object_name'] + ) except KeyError: - messages.error(self.request, _('Error deleting object.')) + messages.error( + self.request, _('Error deleting object.') + ) raise exception else: try: - messages.success(self.request, _('%s deleted successfully.') % self.extra_context['object_name'].capitalize()) + messages.success( + self.request, _('%s deleted successfully.') % self.extra_context['object_name'].capitalize() + ) except KeyError: - messages.success(self.request, _('Object deleted successfully.')) + messages.success( + self.request, _('Object deleted successfully.') + ) return result @@ -365,9 +405,13 @@ class SingleObjectEditView(ViewPermissionCheckMixin, ObjectPermissionCheckMixin, result = super(SingleObjectEditView, self).form_invalid(form) try: - messages.error(self.request, _('Error saving %s details.') % self.extra_context['object_name']) + messages.error( + self.request, _('Error saving %s details.') % self.extra_context['object_name'] + ) except KeyError: - messages.error(self.request, _('Error saving details.')) + messages.error( + self.request, _('Error saving details.') + ) return result @@ -375,9 +419,13 @@ class SingleObjectEditView(ViewPermissionCheckMixin, ObjectPermissionCheckMixin, result = super(SingleObjectEditView, self).form_valid(form) try: - messages.success(self.request, _('%s details saved successfully.') % self.extra_context['object_name'].capitalize()) + messages.success( + self.request, _('%s details saved successfully.') % self.extra_context['object_name'].capitalize() + ) except KeyError: - messages.success(self.request, _('Details saved successfully.')) + messages.success( + self.request, _('Details saved successfully.') + ) return result diff --git a/mayan/apps/common/handlers.py b/mayan/apps/common/handlers.py index 54fb9cf8ff..e992516818 100644 --- a/mayan/apps/common/handlers.py +++ b/mayan/apps/common/handlers.py @@ -7,7 +7,9 @@ from .models import UserLocaleProfile def user_locale_profile_session_config(sender, request, user, **kwargs): - user_locale_profile, created = UserLocaleProfile.objects.get_or_create(user=user) + user_locale_profile, created = UserLocaleProfile.objects.get_or_create( + user=user + ) if not created and user_locale_profile.timezone and user_locale_profile.language: # Don't try to set locale preferences for new users or existing users @@ -16,11 +18,19 @@ def user_locale_profile_session_config(sender, request, user, **kwargs): translation.activate(user_locale_profile.language) if hasattr(request, 'session'): - request.session[translation.LANGUAGE_SESSION_KEY] = user_locale_profile.language - request.session[settings.TIMEZONE_SESSION_KEY] = user_locale_profile.timezone + request.session[ + translation.LANGUAGE_SESSION_KEY + ] = user_locale_profile.language + request.session[ + settings.TIMEZONE_SESSION_KEY + ] = user_locale_profile.timezone else: - request.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_locale_profile.language) - request.set_cookie(settings.TIMEZONE_COOKIE_NAME, user_locale_profile.timezone) + request.set_cookie( + settings.LANGUAGE_COOKIE_NAME, user_locale_profile.language + ) + request.set_cookie( + settings.TIMEZONE_COOKIE_NAME, user_locale_profile.timezone + ) def user_locale_profile_create(sender, instance, created, **kwargs): diff --git a/mayan/apps/common/links.py b/mayan/apps/common/links.py index eb171d2268..d2fc412566 100644 --- a/mayan/apps/common/links.py +++ b/mayan/apps/common/links.py @@ -5,15 +5,30 @@ from django.utils.translation import ugettext_lazy as _ from navigation import Link -def is_superuser(context): - return context['request'].user.is_staff or context['request'].user.is_superuser - - -link_about = Link(icon='fa fa-question', text=_('About'), view='common:about_view') -link_current_user_details = Link(icon='fa fa-user', text=_('User details'), view='common:current_user_details') -link_current_user_edit = Link(icon='fa fa-user', text=_('Edit details'), view='common:current_user_edit') -link_current_user_locale_profile_details = Link(icon='fa fa-globe', text=_('Locale profile'), view='common:current_user_locale_profile_details') -link_current_user_locale_profile_edit = Link(icon='fa fa-globe', text=_('Edit locale profile'), view='common:current_user_locale_profile_edit') -link_license = Link(icon='fa fa-book', text=_('License'), view='common:license_view') -link_setup = Link(icon='fa fa-gear', text=_('Setup'), view='common:setup_list') -link_tools = Link(icon='fa fa-wrench', text=_('Tools'), view='common:tools_list') +link_about = Link( + icon='fa fa-question', text=_('About'), view='common:about_view' +) +link_current_user_details = Link( + icon='fa fa-user', text=_('User details'), + view='common:current_user_details' +) +link_current_user_edit = Link( + icon='fa fa-user', text=_('Edit details'), view='common:current_user_edit' +) +link_current_user_locale_profile_details = Link( + icon='fa fa-globe', text=_('Locale profile'), + view='common:current_user_locale_profile_details' +) +link_current_user_locale_profile_edit = Link( + icon='fa fa-globe', text=_('Edit locale profile'), + view='common:current_user_locale_profile_edit' +) +link_license = Link( + icon='fa fa-book', text=_('License'), view='common:license_view' +) +link_setup = Link( + icon='fa fa-gear', text=_('Setup'), view='common:setup_list' +) +link_tools = Link( + icon='fa fa-wrench', text=_('Tools'), view='common:tools_list' +) diff --git a/mayan/apps/common/mixins.py b/mayan/apps/common/mixins.py index 4cecc035ec..9ad5fb9c4b 100644 --- a/mayan/apps/common/mixins.py +++ b/mayan/apps/common/mixins.py @@ -45,10 +45,14 @@ class ObjectListPermissionFilterMixin(object): if self.object_permission: try: # Check to see if the user has the permissions globally - Permission.check_permissions(self.request.user, (self.object_permission,)) + Permission.check_permissions( + self.request.user, (self.object_permission,) + ) except PermissionDenied: # No global permission, filter ther queryset per object + permission - return AccessControlList.objects.filter_by_access(self.object_permission, self.request.user, queryset) + return AccessControlList.objects.filter_by_access( + self.object_permission, self.request.user, queryset + ) else: # Has the permission globally, return all results return queryset @@ -66,9 +70,14 @@ class ObjectPermissionCheckMixin(object): if self.object_permission: try: - Permission.check_permissions(request.user, (self.object_permission,)) + Permission.check_permissions( + request.user, (self.object_permission,) + ) except PermissionDenied: - AccessControlList.objects.check_access(self.object_permission, request.user, self.get_permission_object()) + AccessControlList.objects.check_access( + self.object_permission, request.user, + self.get_permission_object() + ) return super(ObjectPermissionCheckMixin, self).dispatch(request, *args, **kwargs) @@ -87,8 +96,20 @@ class RedirectionMixin(object): post_action_redirect = self.get_post_action_redirect() action_cancel_redirect = self.get_action_cancel_redirect() - self.next_url = self.request.POST.get('next', self.request.GET.get('next', post_action_redirect if post_action_redirect else self.request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL)))) - self.previous_url = self.request.POST.get('previous', self.request.GET.get('previous', action_cancel_redirect if action_cancel_redirect else self.request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL)))) + self.next_url = self.request.POST.get( + 'next', self.request.GET.get( + 'next', post_action_redirect if post_action_redirect else self.request.META.get( + 'HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL) + ) + ) + ) + self.previous_url = self.request.POST.get( + 'previous', self.request.GET.get( + 'previous', action_cancel_redirect if action_cancel_redirect else self.request.META.get( + 'HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL) + ) + ) + ) return super(RedirectionMixin, self).dispatch(request, *args, **kwargs) @@ -112,6 +133,8 @@ class ViewPermissionCheckMixin(object): def dispatch(self, request, *args, **kwargs): if self.view_permission: - Permission.check_permissions(self.request.user, (self.view_permission,)) + Permission.check_permissions( + self.request.user, (self.view_permission,) + ) return super(ViewPermissionCheckMixin, self).dispatch(request, *args, **kwargs) diff --git a/mayan/apps/common/models.py b/mayan/apps/common/models.py index fb6276ea92..91168072fb 100644 --- a/mayan/apps/common/models.py +++ b/mayan/apps/common/models.py @@ -18,9 +18,14 @@ def upload_to(instance, filename): @python_2_unicode_compatible class SharedUploadedFile(models.Model): - file = models.FileField(storage=shared_storage_backend, upload_to=upload_to, verbose_name=_('File')) + file = models.FileField( + storage=shared_storage_backend, upload_to=upload_to, + verbose_name=_('File') + ) filename = models.CharField(max_length=255, verbose_name=_('Filename')) - datetime = models.DateTimeField(auto_now_add=True, verbose_name=_('Date time')) + datetime = models.DateTimeField( + auto_now_add=True, verbose_name=_('Date time') + ) class Meta: verbose_name = _('Shared uploaded file') @@ -43,10 +48,17 @@ class SharedUploadedFile(models.Model): @python_2_unicode_compatible class UserLocaleProfile(models.Model): - user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='locale_profile', verbose_name=_('User')) - - timezone = models.CharField(choices=zip(common_timezones, common_timezones), max_length=48, verbose_name=_('Timezone')) - language = models.CharField(choices=settings.LANGUAGES, max_length=8, verbose_name=_('Language')) + user = models.OneToOneField( + settings.AUTH_USER_MODEL, related_name='locale_profile', + verbose_name=_('User') + ) + timezone = models.CharField( + choices=zip(common_timezones, common_timezones), max_length=48, + verbose_name=_('Timezone') + ) + language = models.CharField( + choices=settings.LANGUAGES, max_length=8, verbose_name=_('Language') + ) def __str__(self): return unicode(self.user) diff --git a/mayan/apps/common/settings.py b/mayan/apps/common/settings.py index 5db757b07e..633bf70b85 100644 --- a/mayan/apps/common/settings.py +++ b/mayan/apps/common/settings.py @@ -5,5 +5,13 @@ from django.utils.translation import ugettext_lazy as _ from smart_settings import Namespace namespace = Namespace(name='common', label=_('Common')) -setting_temporary_directory = namespace.add_setting(global_name='COMMON_TEMPORARY_DIRECTORY', default='/tmp', help_text=_('Temporary directory used site wide to store thumbnails, previews and temporary files. If none is specified, one will be created using tempfile.mkdtemp()'), is_path=True) # TODO: get os default temp directory -setting_shared_storage = namespace.add_setting(global_name='COMMON_SHARED_STORAGE', default='storage.backends.filebasedstorage.FileBasedStorage', help_text=_('A storage backend that all workers can use to share files.')) +setting_temporary_directory = namespace.add_setting( + global_name='COMMON_TEMPORARY_DIRECTORY', default='/tmp', + help_text=_('Temporary directory used site wide to store thumbnails, previews and temporary files. If none is specified, one will be created using tempfile.mkdtemp()'), + is_path=True +) # TODO: get os default temp directory +setting_shared_storage = namespace.add_setting( + global_name='COMMON_SHARED_STORAGE', + default='storage.backends.filebasedstorage.FileBasedStorage', + help_text=_('A storage backend that all workers can use to share files.') +) diff --git a/mayan/apps/common/urls.py b/mayan/apps/common/urls.py index b9a49197b3..7aa779405d 100644 --- a/mayan/apps/common/urls.py +++ b/mayan/apps/common/urls.py @@ -15,17 +15,38 @@ urlpatterns = patterns( url(r'^$', HomeView.as_view(), name='home'), url(r'^about/$', AboutView.as_view(), name='about_view'), url(r'^license/$', LicenseView.as_view(), name='license_view'), - url(r'^object/multiple/action/$', 'multi_object_action_view', name='multi_object_action_view'), + url( + r'^object/multiple/action/$', 'multi_object_action_view', + name='multi_object_action_view' + ), url(r'^setup/$', SetupListView.as_view(), name='setup_list'), url(r'^tools/$', ToolsListView.as_view(), name='tools_list'), - url(r'^user/$', CurrentUserDetailsView.as_view(), name='current_user_details'), - url(r'^user/edit/$', CurrentUserEditView.as_view(), name='current_user_edit'), - url(r'^user/locale/$', CurrentUserLocaleProfileDetailsView.as_view(), name='current_user_locale_profile_details'), - url(r'^user/locale/edit/$', CurrentUserLocaleProfileEditView.as_view(), name='current_user_locale_profile_edit'), + url( + r'^user/$', CurrentUserDetailsView.as_view(), + name='current_user_details' + ), + url( + r'^user/edit/$', CurrentUserEditView.as_view(), + name='current_user_edit' + ), + url( + r'^user/locale/$', CurrentUserLocaleProfileDetailsView.as_view(), + name='current_user_locale_profile_details' + ), + url( + r'^user/locale/edit/$', CurrentUserLocaleProfileEditView.as_view(), + name='current_user_locale_profile_edit' + ), ) urlpatterns += patterns( '', - url(r'^set_language/$', 'django.views.i18n.set_language', name='set_language'), - (r'^favicon\.ico$', RedirectView.as_view(url=static('appearance/images/favicon.ico'))), + url( + r'^set_language/$', 'django.views.i18n.set_language', + name='set_language' + ), + ( + r'^favicon\.ico$', + RedirectView.as_view(url=static('appearance/images/favicon.ico')) + ), ) diff --git a/mayan/apps/common/utils.py b/mayan/apps/common/utils.py index 5224503281..cda85099c7 100644 --- a/mayan/apps/common/utils.py +++ b/mayan/apps/common/utils.py @@ -37,7 +37,8 @@ def copyfile(source, destination, buffer_size=1024 * 1024): def encapsulate(function): # Workaround Django ticket 15791 # Changeset 16045 - # http://stackoverflow.com/questions/6861601/cannot-resolve-callable-context-variable/6955045#6955045 + # http://stackoverflow.com/questions/6861601/ + # cannot-resolve-callable-context-variable/6955045#6955045 return lambda: function diff --git a/mayan/apps/common/views.py b/mayan/apps/common/views.py index 06e53a9eff..89030ee068 100644 --- a/mayan/apps/common/views.py +++ b/mayan/apps/common/views.py @@ -53,9 +53,13 @@ class CurrentUserLocaleProfileDetailsView(TemplateView): template_name = 'appearance/generic_form.html' def get_context_data(self, **kwargs): - data = super(CurrentUserLocaleProfileDetailsView, self).get_context_data(**kwargs) + data = super( + CurrentUserLocaleProfileDetailsView, self + ).get_context_data(**kwargs) data.update({ - 'form': LocaleProfileForm_view(instance=self.request.user.locale_profile), + 'form': LocaleProfileForm_view( + instance=self.request.user.locale_profile + ), 'read_only': True, 'title': _('Current user locale profile details'), }) @@ -63,9 +67,13 @@ class CurrentUserLocaleProfileDetailsView(TemplateView): class CurrentUserLocaleProfileEditView(SingleObjectEditView): - extra_context = {'title': _('Edit current user locale profile details')} + extra_context = { + 'title': _('Edit current user locale profile details') + } form_class = LocaleProfileForm - post_action_redirect = reverse_lazy('common:current_user_locale_profile_details') + post_action_redirect = reverse_lazy( + 'common:current_user_locale_profile_details' + ) def form_valid(self, form): form.save() @@ -74,11 +82,19 @@ class CurrentUserLocaleProfileEditView(SingleObjectEditView): translation.activate(form.cleaned_data['language']) if hasattr(self.request, 'session'): - self.request.session[translation.LANGUAGE_SESSION_KEY] = form.cleaned_data['language'] - self.request.session[settings.TIMEZONE_SESSION_KEY] = form.cleaned_data['timezone'] + self.request.session[ + translation.LANGUAGE_SESSION_KEY + ] = form.cleaned_data['language'] + self.request.session[ + settings.TIMEZONE_SESSION_KEY + ] = form.cleaned_data['timezone'] else: - self.request.set_cookie(settings.LANGUAGE_COOKIE_NAME, form.cleaned_data['language']) - self.request.set_cookie(settings.TIMEZONE_COOKIE_NAME, form.cleaned_data['timezone']) + self.request.set_cookie( + settings.LANGUAGE_COOKIE_NAME, form.cleaned_data['language'] + ) + self.request.set_cookie( + settings.TIMEZONE_COOKIE_NAME, form.cleaned_data['timezone'] + ) return super(CurrentUserLocaleProfileEditView, self).form_valid(form) @@ -95,14 +111,18 @@ class HomeView(TemplateView): 'hide_links': True, 'search_results_limit': 100, 'search_terms': self.request.GET.get('q'), - 'missing_list': [item for item in MissingItem.get_all() if item.condition()], + 'missing_list': [ + item for item in MissingItem.get_all() if item.condition() + ], }) return data def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) - queryset, ids, timedelta = document_search.search(request.GET, request.user) + queryset, ids, timedelta = document_search.search( + request.GET, request.user + ) # Update the context with the search results context.update({ @@ -129,7 +149,9 @@ class SetupListView(TemplateView): def get_context_data(self, **kwargs): data = super(SetupListView, self).get_context_data(**kwargs) data.update({ - 'resolved_links': menu_setup.resolve(context=RequestContext(self.request)), + 'resolved_links': menu_setup.resolve( + context=RequestContext(self.request) + ), 'title': _('Setup items'), }) return data @@ -154,26 +176,49 @@ def multi_object_action_view(request): then redirects to the appropiate specialized view """ - next = request.POST.get('next', request.GET.get('next', 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) + ) + ) + ) action = request.GET.get('action', None) id_list = ','.join([key[3:] for key in request.GET.keys() if key.startswith('pk_')]) - items_property_list = [loads(key[11:]) for key in request.GET.keys() if key.startswith('properties_')] + items_property_list = [ + (key[11:]) for key in request.GET.keys() if key.startswith('properties_') + ] if not action: messages.error(request, _('No action selected.')) - return HttpResponseRedirect(request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))) + return HttpResponseRedirect( + request.META.get( + 'HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL) + ) + ) if not id_list and not items_property_list: messages.error(request, _('Must select at least one item.')) - return HttpResponseRedirect(request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))) + return HttpResponseRedirect( + request.META.get( + 'HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL) + ) + ) # Separate redirects to keep backwards compatibility with older # functions that don't expect a properties_list parameter if items_property_list: - return HttpResponseRedirect('%s?%s' % ( - action, - urlencode({'items_property_list': dumps(items_property_list), 'next': next})) + return HttpResponseRedirect( + '%s?%s' % ( + action, + urlencode( + { + 'items_property_list': dumps(items_property_list), + 'next': next + } + ) + ) ) else: return HttpResponseRedirect('%s?%s' % ( diff --git a/mayan/apps/common/widgets.py b/mayan/apps/common/widgets.py index df155cc8b8..477c7dd85e 100644 --- a/mayan/apps/common/widgets.py +++ b/mayan/apps/common/widgets.py @@ -118,7 +118,10 @@ class ScrollableCheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple): value = [] has_id = attrs and 'id' in attrs final_attrs = self.build_attrs(attrs, name=name) - output = ['
    '] + # TODO: Move this styling to a CSS class + output = [ + '
      ' + ] # Normalize to strings str_values = set([force_unicode(v) for v in value]) for i, (option_value, option_label) in enumerate(chain(self.choices, choices)): @@ -130,14 +133,22 @@ class ScrollableCheckboxSelectMultiple(forms.widgets.CheckboxSelectMultiple): else: label_for = '' - cb = forms.widgets.CheckboxInput(final_attrs, check_test=lambda value: value in str_values) + cb = forms.widgets.CheckboxInput( + final_attrs, check_test=lambda value: value in str_values + ) option_value = force_unicode(option_value) rendered_cb = cb.render(name, option_value) option_label = conditional_escape(force_unicode(option_label)) - output.append('
    • %s %s
    • ' % (label_for, rendered_cb, option_label)) + output.append( + '
    • %s %s
    • ' % ( + label_for, rendered_cb, option_label + ) + ) output.append('
    ') - return mark_safe('
    %s
    ' % '\n'.join(output)) + return mark_safe( + '
    %s
    ' % '\n'.join(output) + ) class TextAreaDiv(forms.widgets.Widget): diff --git a/mayan/apps/converter/classes.py b/mayan/apps/converter/classes.py index bf44aa20fd..ff2d2a7826 100644 --- a/mayan/apps/converter/classes.py +++ b/mayan/apps/converter/classes.py @@ -83,7 +83,9 @@ class ConverterBase(object): if not os.path.exists(setting_libreoffice_path.value): raise OfficeConversionError( - _('LibreOffice not installed or not found at path: %s') % setting_libreoffice_path.value + _( + 'LibreOffice not installed or not found at path: %s' + ) % setting_libreoffice_path.value ) new_file_object, input_filepath = tempfile.mkstemp() diff --git a/mayan/apps/document_indexing/literals.py b/mayan/apps/document_indexing/literals.py index a227799d07..b170d49de7 100644 --- a/mayan/apps/document_indexing/literals.py +++ b/mayan/apps/document_indexing/literals.py @@ -1 +1 @@ -RETRY_DELAY = 20 # TODO: convert this into a config option +RETRY_DELAY = 5 # TODO: convert this into a config option diff --git a/mayan/apps/document_states/views.py b/mayan/apps/document_states/views.py index 600cb68fc2..5c62968c7d 100644 --- a/mayan/apps/document_states/views.py +++ b/mayan/apps/document_states/views.py @@ -97,11 +97,10 @@ class WorkflowInstanceDetailView(SingleObjectListView): def get_context_data(self, **kwargs): context = { + 'hide_object': True, 'navigation_object_list': ['object', 'workflow_instance'], 'object': self.get_workflow_instance().document, 'object_list': self.get_queryset(), - 'title': _('Log entries'), - 'hide_object': True, 'title': _('Detail of workflow: %(workflow)s') % { 'workflow': self.get_workflow_instance() }, diff --git a/mayan/apps/documents/admin.py b/mayan/apps/documents/admin.py index 7dbef01780..5c7bf2e9c4 100644 --- a/mayan/apps/documents/admin.py +++ b/mayan/apps/documents/admin.py @@ -3,8 +3,8 @@ from __future__ import unicode_literals from django.contrib import admin from .models import ( - DeletedDocument, Document, DocumentPage, DocumentType, DocumentTypeFilename, - DocumentVersion, RecentDocument + DeletedDocument, Document, DocumentPage, DocumentType, + DocumentTypeFilename, DocumentVersion, RecentDocument ) @@ -50,7 +50,10 @@ class DocumentTypeAdmin(admin.ModelAdmin): inlines = ( DocumentTypeFilenameInline, ) - list_display = ('label', 'trash_time_period', 'trash_time_unit', 'delete_time_period', 'delete_time_unit') + list_display = ( + 'label', 'trash_time_period', 'trash_time_unit', 'delete_time_period', + 'delete_time_unit' + ) class RecentDocumentAdmin(admin.ModelAdmin): diff --git a/mayan/apps/documents/api_views.py b/mayan/apps/documents/api_views.py index 86f408496f..1f39864fe3 100644 --- a/mayan/apps/documents/api_views.py +++ b/mayan/apps/documents/api_views.py @@ -67,12 +67,18 @@ class APIDocumentListView(generics.ListCreateAPIView): return super(APIDocumentListView, 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(): - document_type = get_object_or_404(DocumentType, pk=serializer.data['document_type']) + document_type = get_object_or_404( + DocumentType, pk=serializer.data['document_type'] + ) - logger.info('Creating document version for document type: %s', document_type) + logger.info( + 'Creating document version for document type: %s', document_type + ) document = Document.objects.create( description=serializer.data['description'] or '', @@ -91,13 +97,17 @@ class APIDocumentListView(generics.ListCreateAPIView): document_id=document.pk, user_id=request.user.pk, ) - logger.info('New document version queued for document: %s', document) + logger.info( + 'New document version queued for document: %s', document + ) serializer.object = document 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) @@ -147,7 +157,9 @@ class APIDocumentVersionCreateView(generics.CreateAPIView): mayan_view_permissions = {'POST': [permission_document_new_version]} 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(): # Nested resource we take the document pk from the URL and insert it @@ -155,7 +167,10 @@ class APIDocumentVersionCreateView(generics.CreateAPIView): # a read only field in the serializer document = get_object_or_404(Document, pk=kwargs['pk']) - document.new_version(file_object=serializer.object.file, comment=serializer.object.comment, _user=request.user) + document.new_version( + file_object=serializer.object.file, + comment=serializer.object.comment, _user=request.user + ) headers = self.get_success_headers(serializer.data) return Response(status=status.HTTP_202_ACCEPTED, headers=headers) @@ -191,9 +206,13 @@ class APIDocumentImageView(generics.GenericAPIView): document = get_object_or_404(Document, pk=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, document) + AccessControlList.objects.check_access( + permission_document_view, request.user, document + ) size = request.GET.get('size', setting_display_size.value) @@ -214,7 +233,12 @@ class APIDocumentImageView(generics.GenericAPIView): document_page = document.pages.get(page_number=page) try: - task = task_get_document_page_image.apply_async(kwargs=dict(document_page_id=document_page.pk, size=size, zoom=zoom, rotation=rotation, as_base64=True, version=version)) + task = task_get_document_page_image.apply_async( + kwargs=dict( + document_page_id=document_page.pk, size=size, zoom=zoom, + rotation=rotation, as_base64=True, version=version + ) + ) # TODO: prepend 'data:%s;base64,%s' based on format specified in # async call return Response({ @@ -222,9 +246,19 @@ class APIDocumentImageView(generics.GenericAPIView): 'data': task.get(timeout=DOCUMENT_IMAGE_TASK_TIMEOUT) }) except UnknownFileFormat as exception: - return Response({'status': 'error', 'detail': 'unknown_file_format', 'message': unicode(exception)}) + return Response( + { + 'status': 'error', 'detail': 'unknown_file_format', + 'message': unicode(exception) + } + ) except UnkownConvertError as exception: - return Response({'status': 'error', 'detail': 'converter_error', 'message': unicode(exception)}) + return Response( + { + 'status': 'error', 'detail': 'converter_error', + 'message': unicode(exception) + } + ) class APIDocumentPageView(generics.RetrieveUpdateAPIView): @@ -322,9 +356,13 @@ class APIDocumentTypeDocumentListView(generics.ListAPIView): def get_queryset(self): document_type = get_object_or_404(DocumentType, pk=self.kwargs['pk']) try: - Permission.check_permissions(self.request.user, [permission_document_type_view]) + Permission.check_permissions( + self.request.user, [permission_document_type_view] + ) except PermissionDenied: - AccessControlList.objects.check_access(permission_document_type_view, self.request.user, document_type) + AccessControlList.objects.check_access( + permission_document_type_view, self.request.user, document_type + ) return document_type.documents.all() diff --git a/mayan/apps/documents/apps.py b/mayan/apps/documents/apps.py index 1b161263ed..4c7ad12ade 100644 --- a/mayan/apps/documents/apps.py +++ b/mayan/apps/documents/apps.py @@ -58,8 +58,8 @@ from .links import ( ) from .literals import CHECK_DELETE_PERIOD_INTERVAL, CHECK_TRASH_PERIOD_INTERVAL from .models import ( - DeletedDocument, Document, DocumentPage, DocumentType, DocumentTypeFilename, - DocumentVersion + DeletedDocument, Document, DocumentPage, DocumentType, + DocumentTypeFilename, DocumentVersion ) from .permissions import ( permission_document_delete, permission_document_download, @@ -82,9 +82,16 @@ class DocumentsApp(MayanAppConfig): APIEndPoint('documents') - 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(), view='documents:document_type_list') + 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(), + view='documents:document_type_list' + ) - ModelAttribute(Document, label=_('Label'), name='label', type_name='field') + ModelAttribute( + Document, label=_('Label'), name='label', type_name='field' + ) ModelPermission.register( model=Document, permissions=( @@ -108,16 +115,34 @@ class DocumentsApp(MayanAppConfig): model=Document, related='document_type', ) - SourceColumn(source=Document, label=_('Thumbnail'), attribute=encapsulate(lambda document: document_thumbnail(document, gallery_name='documents:document_list', title=getattr(document, 'label', None), size=setting_thumbnail_size.value))) - SourceColumn(source=Document, label=_('Type'), attribute='document_type') - SourceColumn(source=DeletedDocument, label=_('Type'), attribute='document_type') - SourceColumn(source=DeletedDocument, label=_('Date time trashed'), attribute='deleted_date_time') + SourceColumn( + source=Document, label=_('Thumbnail'), + attribute=encapsulate( + lambda document: document_thumbnail( + document, gallery_name='documents:document_list', + size=setting_thumbnail_size.value, + title=getattr(document, 'label', None), + ) + ) + ) + SourceColumn( + source=Document, label=_('Type'), attribute='document_type' + ) + SourceColumn( + source=DeletedDocument, label=_('Type'), attribute='document_type' + ) + SourceColumn( + source=DeletedDocument, label=_('Date time trashed'), + attribute='deleted_date_time' + ) app.conf.CELERYBEAT_SCHEDULE.update( { 'task_check_delete_periods': { 'task': 'documents.tasks.task_check_delete_periods', - 'schedule': timedelta(seconds=CHECK_DELETE_PERIOD_INTERVAL), + 'schedule': timedelta( + seconds=CHECK_DELETE_PERIOD_INTERVAL + ), }, 'task_check_trash_periods': { 'task': 'documents.tasks.task_check_trash_periods', @@ -128,8 +153,14 @@ class DocumentsApp(MayanAppConfig): app.conf.CELERY_QUEUES.extend( ( - Queue('converter', Exchange('converter'), routing_key='converter', delivery_mode=1), - Queue('documents_periodic', Exchange('documents_periodic'), routing_key='documents_periodic', delivery_mode=1), + Queue( + 'converter', Exchange('converter'), + routing_key='converter', delivery_mode=1 + ), + Queue( + 'documents_periodic', Exchange('documents_periodic'), + routing_key='documents_periodic', delivery_mode=1 + ), Queue('uploads', Exchange('uploads'), routing_key='uploads'), ) ) @@ -157,44 +188,135 @@ class DocumentsApp(MayanAppConfig): } ) - menu_front_page.bind_links(links=[link_document_list_recent, link_document_list, link_document_list_deleted]) + menu_front_page.bind_links( + links=[ + link_document_list_recent, link_document_list, + link_document_list_deleted + ] + ) menu_setup.bind_links(links=[link_document_type_setup]) menu_tools.bind_links(links=[link_clear_image_cache]) # Document type links - menu_object.bind_links(links=[link_document_type_edit, link_document_type_filename_list, link_acl_list, link_document_type_delete], sources=[DocumentType]) - menu_object.bind_links(links=[link_document_type_filename_edit, link_document_type_filename_delete], sources=[DocumentTypeFilename]) - menu_secondary.bind_links(links=[link_document_type_list, link_document_type_create], sources=[DocumentType, 'documents:document_type_create', 'documents:document_type_list']) - menu_sidebar.bind_links(links=[link_document_type_filename_create], sources=[DocumentTypeFilename, 'documents:document_type_filename_list', 'documents:document_type_filename_create']) - - menu_sidebar.bind_links(links=[link_trash_can_empty], sources=['documents:document_list_deleted', 'documents:trash_can_empty']) + menu_object.bind_links( + links=[ + link_document_type_edit, link_document_type_filename_list, + link_acl_list, link_document_type_delete + ], sources=[DocumentType] + ) + menu_object.bind_links( + links=[ + link_document_type_filename_edit, + link_document_type_filename_delete + ], sources=[DocumentTypeFilename] + ) + menu_secondary.bind_links( + links=[link_document_type_list, link_document_type_create], + sources=[ + DocumentType, 'documents:document_type_create', + 'documents:document_type_list' + ] + ) + menu_sidebar.bind_links( + links=[link_document_type_filename_create], + sources=[ + DocumentTypeFilename, 'documents:document_type_filename_list', + 'documents:document_type_filename_create' + ] + ) + menu_sidebar.bind_links( + links=[link_trash_can_empty], + sources=[ + 'documents:document_list_deleted', 'documents:trash_can_empty' + ] + ) # Document object links - menu_object.bind_links(links=[link_document_edit, link_document_document_type_edit, link_document_print, link_document_trash, link_document_download, link_document_clear_transformations, link_document_update_page_count], sources=[Document]) - menu_object.bind_links(links=[link_document_restore, link_document_delete], sources=[DeletedDocument]) + menu_object.bind_links( + links=[ + link_document_edit, link_document_document_type_edit, + link_document_print, link_document_trash, + link_document_download, link_document_clear_transformations, + link_document_update_page_count + ], sources=[Document] + ) + menu_object.bind_links( + links=[link_document_restore, link_document_delete], + sources=[DeletedDocument] + ) # Document facet links menu_facet.bind_links(links=[link_acl_list], sources=[Document]) - menu_facet.bind_links(links=[link_document_preview], sources=[Document], position=0) - menu_facet.bind_links(links=[link_document_properties], sources=[Document], position=2) - menu_facet.bind_links(links=[link_events_for_object, link_document_version_list], sources=[Document], position=2) + menu_facet.bind_links( + links=[link_document_preview], sources=[Document], position=0 + ) + menu_facet.bind_links( + links=[link_document_properties], sources=[Document], position=2 + ) + menu_facet.bind_links( + links=[link_events_for_object, link_document_version_list], + sources=[Document], position=2 + ) menu_facet.bind_links(links=[link_document_pages], sources=[Document]) # Document actions - menu_object.bind_links(links=[link_document_version_revert, link_document_version_download], sources=[DocumentVersion]) - menu_multi_item.bind_links(links=[link_document_multiple_clear_transformations, link_document_multiple_trash, link_document_multiple_download, link_document_multiple_update_page_count, link_document_multiple_document_type_edit], sources=[Document]) - menu_multi_item.bind_links(links=[link_document_multiple_restore, link_document_multiple_delete], sources=[DeletedDocument]) + menu_object.bind_links( + links=[ + link_document_version_revert, link_document_version_download + ], + sources=[DocumentVersion] + ) + menu_multi_item.bind_links( + links=[ + link_document_multiple_clear_transformations, + link_document_multiple_trash, link_document_multiple_download, + link_document_multiple_update_page_count, + link_document_multiple_document_type_edit + ], sources=[Document] + ) + menu_multi_item.bind_links( + links=[ + link_document_multiple_restore, link_document_multiple_delete + ], sources=[DeletedDocument] + ) # Document pages - menu_facet.bind_links(links=[link_document_page_rotate_left, link_document_page_rotate_right, link_document_page_zoom_in, link_document_page_zoom_out, link_document_page_view_reset], sources=['documents:document_page_view']) - menu_facet.bind_links(links=[link_document_page_return, link_document_page_view], sources=[DocumentPage]) - menu_facet.bind_links(links=[link_document_page_navigation_first, link_document_page_navigation_previous, link_document_page_navigation_next, link_document_page_navigation_last, link_transformation_list], sources=[DocumentPage]) - menu_object.bind_links(links=[link_transformation_list], sources=[DocumentPage]) + menu_facet.bind_links( + links=[ + link_document_page_rotate_left, + link_document_page_rotate_right, link_document_page_zoom_in, + link_document_page_zoom_out, link_document_page_view_reset + ], sources=['documents:document_page_view'] + ) + menu_facet.bind_links( + links=[link_document_page_return, link_document_page_view], + sources=[DocumentPage] + ) + menu_facet.bind_links( + links=[ + link_document_page_navigation_first, + link_document_page_navigation_previous, + link_document_page_navigation_next, + link_document_page_navigation_last, link_transformation_list + ], sources=[DocumentPage] + ) + menu_object.bind_links( + links=[link_transformation_list], sources=[DocumentPage] + ) namespace = StatisticNamespace(name='documents', label=_('Documents')) - namespace.add_statistic(DocumentStatistics(name='document_stats', label=_('Document tendencies'))) - namespace.add_statistic(DocumentUsageStatistics(name='document_usage', label=_('Document usage'))) + namespace.add_statistic( + DocumentStatistics( + name='document_stats', label=_('Document tendencies') + ) + ) + namespace.add_statistic(DocumentUsageStatistics( + name='document_usage', label=_('Document usage')) + ) - post_initial_setup.connect(create_default_document_type, dispatch_uid='create_default_document_type') + post_initial_setup.connect( + create_default_document_type, + dispatch_uid='create_default_document_type' + ) registry.register(Document) diff --git a/mayan/apps/documents/events.py b/mayan/apps/documents/events.py index 4f0c518022..c7856f6d65 100644 --- a/mayan/apps/documents/events.py +++ b/mayan/apps/documents/events.py @@ -4,8 +4,19 @@ from django.utils.translation import ugettext_lazy as _ from events.classes import Event -event_document_create = Event(name='documents_document_create', label=_('Document created')) -event_document_properties_edit = Event(name='documents_document_edit', label=_('Document properties edited')) -event_document_type_change = Event(name='documents_document_type_change', label=_('Document type changed')) -event_document_new_version = Event(name='documents_document_new_version', label=_('New version uploaded')) -event_document_version_revert = Event(name='documents_document_version_revert', label=_('Document version reverted')) +event_document_create = Event( + name='documents_document_create', label=_('Document created') +) +event_document_properties_edit = Event( + name='documents_document_edit', label=_('Document properties edited') +) +event_document_type_change = Event( + name='documents_document_type_change', label=_('Document type changed') +) +event_document_new_version = Event( + name='documents_document_new_version', label=_('New version uploaded') +) +event_document_version_revert = Event( + name='documents_document_version_revert', + label=_('Document version reverted') +) diff --git a/mayan/apps/documents/forms.py b/mayan/apps/documents/forms.py index 321cbec3d1..4687cc3a85 100644 --- a/mayan/apps/documents/forms.py +++ b/mayan/apps/documents/forms.py @@ -71,7 +71,8 @@ class DocumentForm(forms.ModelForm): self.fields['document_type_available_filenames'] = forms.ModelChoiceField( queryset=filenames_qs, required=False, - label=_('Quick document rename')) + label=_('Quick document rename') + ) class DocumentPropertiesForm(DetailForm): @@ -88,7 +89,9 @@ class DocumentTypeSelectForm(forms.Form): Form to select the document type of a document to be created, used as form #1 in the document creation wizard """ - document_type = forms.ModelChoiceField(queryset=DocumentType.objects.all(), label=('Document type')) + document_type = forms.ModelChoiceField( + queryset=DocumentType.objects.all(), label=('Document type') + ) class PrintForm(forms.Form): @@ -114,8 +117,15 @@ 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.')) - 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.')) + 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.') + ) + 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.') + ) def __init__(self, *args, **kwargs): self.document_versions = kwargs.pop('document_versions', None) diff --git a/mayan/apps/metadata/classes.py b/mayan/apps/metadata/classes.py index d85bd60827..a1b185bc51 100644 --- a/mayan/apps/metadata/classes.py +++ b/mayan/apps/metadata/classes.py @@ -18,4 +18,6 @@ class DocumentMetadataHelper(object): try: return self.instance.metadata.get(metadata_type__name=name).value except MetadataType.DoesNotExist: - raise AttributeError(_('\'metadata\' object has no attribute \'%s\'') % name) + raise AttributeError( + _('\'metadata\' object has no attribute \'%s\'') % name + ) diff --git a/mayan/apps/metadata/links.py b/mayan/apps/metadata/links.py index 6943b8a0b2..2c3d7898c1 100644 --- a/mayan/apps/metadata/links.py +++ b/mayan/apps/metadata/links.py @@ -12,17 +12,61 @@ from .permissions import ( permission_metadata_type_edit, permission_metadata_type_view ) -link_documents_missing_required_metadata = Link(icon='fa fa-edit', text=_('Missing metadata'), view='metadata:documents_missing_required_metadata') -link_metadata_add = Link(permissions=[permission_metadata_document_add], text=_('Add metadata'), view='metadata:metadata_add', args='object.pk') -link_metadata_edit = Link(permissions=[permission_metadata_document_edit], text=_('Edit metadata'), view='metadata:metadata_edit', args='object.pk') -link_metadata_multiple_add = Link(permissions=[permission_metadata_document_add], text=_('Add metadata'), view='metadata:metadata_multiple_add') -link_metadata_multiple_edit = Link(permissions=[permission_metadata_document_edit], text=_('Edit metadata'), view='metadata:metadata_multiple_edit') -link_metadata_multiple_remove = Link(permissions=[permission_metadata_document_remove], text=_('Remove metadata'), view='metadata:metadata_multiple_remove') -link_metadata_remove = Link(permissions=[permission_metadata_document_remove], text=_('Remove metadata'), view='metadata:metadata_remove', args='object.pk') -link_metadata_view = Link(permissions=[permission_metadata_document_view], text=_('Metadata'), view='metadata:metadata_view', args='object.pk') -link_setup_document_type_metadata = Link(permissions=[permission_document_type_edit], text=_('Optional metadata'), view='metadata:setup_document_type_metadata', args='resolved_object.pk') -link_setup_document_type_metadata_required = Link(permissions=[permission_document_type_edit], text=_('Required metadata'), view='metadata:setup_document_type_metadata_required', args='resolved_object.pk') -link_setup_metadata_type_create = Link(permissions=[permission_metadata_type_create], text=_('Create new'), view='metadata:setup_metadata_type_create') -link_setup_metadata_type_delete = Link(permissions=[permission_metadata_type_delete], tags='dangerous', text=_('Delete'), view='metadata:setup_metadata_type_delete', args='object.pk') -link_setup_metadata_type_edit = Link(permissions=[permission_metadata_type_edit], text=_('Edit'), view='metadata:setup_metadata_type_edit', args='object.pk') -link_setup_metadata_type_list = Link(icon='fa fa-pencil', permissions=[permission_metadata_type_view], text=_('Metadata types'), view='metadata:setup_metadata_type_list') +link_documents_missing_required_metadata = Link( + icon='fa fa-edit', text=_('Missing metadata'), + view='metadata:documents_missing_required_metadata' +) +link_metadata_add = Link( + permissions=[permission_metadata_document_add], text=_('Add metadata'), + view='metadata:metadata_add', args='object.pk' +) +link_metadata_edit = Link( + permissions=[permission_metadata_document_edit], text=_('Edit metadata'), + view='metadata:metadata_edit', args='object.pk' +) +link_metadata_multiple_add = Link( + permissions=[permission_metadata_document_add], text=_('Add metadata'), + view='metadata:metadata_multiple_add' +) +link_metadata_multiple_edit = Link( + permissions=[permission_metadata_document_edit], text=_('Edit metadata'), + view='metadata:metadata_multiple_edit' +) +link_metadata_multiple_remove = Link( + permissions=[permission_metadata_document_remove], + text=_('Remove metadata'), view='metadata:metadata_multiple_remove' +) +link_metadata_remove = Link( + permissions=[permission_metadata_document_remove], + text=_('Remove metadata'), view='metadata:metadata_remove', + args='object.pk' +) +link_metadata_view = Link( + permissions=[permission_metadata_document_view], text=_('Metadata'), + view='metadata:metadata_view', args='object.pk' +) +link_setup_document_type_metadata = Link( + permissions=[permission_document_type_edit], text=_('Optional metadata'), + view='metadata:setup_document_type_metadata', args='resolved_object.pk' +) +link_setup_document_type_metadata_required = Link( + permissions=[permission_document_type_edit], text=_('Required metadata'), + view='metadata:setup_document_type_metadata_required', + args='resolved_object.pk' +) +link_setup_metadata_type_create = Link( + permissions=[permission_metadata_type_create], text=_('Create new'), + view='metadata:setup_metadata_type_create' +) +link_setup_metadata_type_delete = Link( + permissions=[permission_metadata_type_delete], tags='dangerous', + text=_('Delete'), view='metadata:setup_metadata_type_delete', + args='object.pk') +link_setup_metadata_type_edit = Link( + permissions=[permission_metadata_type_edit], text=_('Edit'), + view='metadata:setup_metadata_type_edit', args='object.pk' +) +link_setup_metadata_type_list = Link( + icon='fa fa-pencil', permissions=[permission_metadata_type_view], + text=_('Metadata types'), view='metadata:setup_metadata_type_list' +) diff --git a/mayan/apps/metadata/permissions.py b/mayan/apps/metadata/permissions.py index bfc98ca4a2..90b74710c7 100644 --- a/mayan/apps/metadata/permissions.py +++ b/mayan/apps/metadata/permissions.py @@ -5,13 +5,29 @@ from django.utils.translation import ugettext_lazy as _ from permissions import PermissionNamespace namespace = PermissionNamespace('metadata', _('Metadata')) -permission_metadata_document_edit = namespace.add_permission(name='metadata_document_edit', label=_('Edit a document\'s metadata')) -permission_metadata_document_add = namespace.add_permission(name='metadata_document_add', label=_('Add metadata to a document')) -permission_metadata_document_remove = namespace.add_permission(name='metadata_document_remove', label=_('Remove metadata from a document')) -permission_metadata_document_view = namespace.add_permission(name='metadata_document_view', label=_('View metadata from a document')) +permission_metadata_document_edit = namespace.add_permission( + name='metadata_document_edit', label=_('Edit a document\'s metadata') +) +permission_metadata_document_add = namespace.add_permission( + name='metadata_document_add', label=_('Add metadata to a document')) +permission_metadata_document_remove = namespace.add_permission( + name='metadata_document_remove', + label=_('Remove metadata from a document') +) +permission_metadata_document_view = namespace.add_permission( + name='metadata_document_view', label=_('View metadata from a document') +) setup_namespace = PermissionNamespace('metadata_setup', _('Metadata setup')) -permission_metadata_type_edit = setup_namespace.add_permission(name='metadata_type_edit', label=_('Edit metadata types')) -permission_metadata_type_create = setup_namespace.add_permission(name='metadata_type_create', label=_('Create new metadata types')) -permission_metadata_type_delete = setup_namespace.add_permission(name='metadata_type_delete', label=_('Delete metadata types')) -permission_metadata_type_view = setup_namespace.add_permission(name='metadata_type_view', label=_('View metadata types')) +permission_metadata_type_edit = setup_namespace.add_permission( + name='metadata_type_edit', label=_('Edit metadata types') +) +permission_metadata_type_create = setup_namespace.add_permission( + name='metadata_type_create', label=_('Create new metadata types') +) +permission_metadata_type_delete = setup_namespace.add_permission( + name='metadata_type_delete', label=_('Delete metadata types') +) +permission_metadata_type_view = setup_namespace.add_permission( + name='metadata_type_view', label=_('View metadata types') +) diff --git a/mayan/apps/ocr/api.py b/mayan/apps/ocr/api.py index 726de49ea4..b6e8badc93 100644 --- a/mayan/apps/ocr/api.py +++ b/mayan/apps/ocr/api.py @@ -16,7 +16,9 @@ from .settings import UNPAPER_PATH logger = logging.getLogger(__name__) try: - UNPAPER = sh.Command(UNPAPER_PATH).bake(overwrite=True, no_multi_pages=True) + UNPAPER = sh.Command(UNPAPER_PATH).bake( + overwrite=True, no_multi_pages=True + ) except sh.CommandNotFound: logger.debug('unpaper not found') UNPAPER = None @@ -37,7 +39,9 @@ def execute_unpaper(input_filepath, output_filepath=None): """ if UNPAPER: if not output_filepath: - fd, output_filepath = tempfile.mkstemp(dir=setting_temporary_directory.value) + fd, output_filepath = tempfile.mkstemp( + dir=setting_temporary_directory.value + ) try: UNPAPER(input_filepath, output_filepath) diff --git a/mayan/apps/ocr/api_views.py b/mayan/apps/ocr/api_views.py index 4647ade860..e677352373 100644 --- a/mayan/apps/ocr/api_views.py +++ b/mayan/apps/ocr/api_views.py @@ -24,15 +24,24 @@ class DocumentVersionOCRView(generics.GenericAPIView): def post(self, request, *args, **kwargs): """Submit document version for OCR.""" - serializer = self.get_serializer(data=request.DATA, files=request.FILES) + serializer = self.get_serializer( + data=request.DATA, files=request.FILES + ) if serializer.is_valid(): - document_version = get_object_or_404(DocumentVersion, pk=serializer.data['document_version_id']) + document_version = get_object_or_404( + DocumentVersion, pk=serializer.data['document_version_id'] + ) try: - Permission.check_permissions(request.user, [permission_ocr_document]) + Permission.check_permissions( + request.user, [permission_ocr_document] + ) except PermissionDenied: - AccessControlList.objects.check_access(permission_ocr_document, request.user, document_version.document) + AccessControlList.objects.check_access( + permission_ocr_document, request.user, + document_version.document + ) document_version.submit_for_ocr() diff --git a/mayan/apps/ocr/apps.py b/mayan/apps/ocr/apps.py index 5953e865ea..1e7a17136b 100644 --- a/mayan/apps/ocr/apps.py +++ b/mayan/apps/ocr/apps.py @@ -32,7 +32,9 @@ from .links import ( ) from .models import DocumentVersionOCRError from .permissions import permission_ocr_document, permission_ocr_content_view -from .settings import setting_pdftotext_path, setting_tesseract_path, setting_unpaper_path +from .settings import ( + setting_pdftotext_path, setting_tesseract_path, setting_unpaper_path +) from .tasks import task_do_ocr logger = logging.getLogger(__name__) @@ -56,7 +58,9 @@ class OCRApp(MayanAppConfig): APIEndPoint('ocr') Document.add_to_class('submit_for_ocr', document_ocr_submit) - DocumentVersion.add_to_class('submit_for_ocr', document_version_ocr_submit) + DocumentVersion.add_to_class( + 'submit_for_ocr', document_version_ocr_submit + ) ModelPermission.register( model=Document, permissions=( @@ -64,9 +68,20 @@ class OCRApp(MayanAppConfig): ) ) - SourceColumn(source=DocumentVersionOCRError, label=_('Document'), attribute=encapsulate(lambda entry: document_link(entry.document_version.document))) - SourceColumn(source=DocumentVersionOCRError, label=_('Added'), attribute='datetime_submitted') - SourceColumn(source=DocumentVersionOCRError, label=_('Result'), attribute='result') + SourceColumn( + source=DocumentVersionOCRError, label=_('Document'), + attribute=encapsulate( + lambda entry: document_link(entry.document_version.document) + ) + ) + SourceColumn( + source=DocumentVersionOCRError, label=_('Added'), + attribute='datetime_submitted' + ) + SourceColumn( + source=DocumentVersionOCRError, label=_('Result'), + attribute='result' + ) app.conf.CELERY_QUEUES.append( Queue('ocr', Exchange('ocr'), routing_key='ocr'), @@ -80,45 +95,98 @@ class OCRApp(MayanAppConfig): } ) - document_search.add_model_field(field='versions__pages__ocr_content__content', label=_('Content')) + document_search.add_model_field( + field='versions__pages__ocr_content__content', label=_('Content') + ) - menu_facet.bind_links(links=[link_document_content], sources=[Document]) - menu_multi_item.bind_links(links=[link_document_submit_multiple], sources=[Document]) - menu_multi_item.bind_links(links=[link_entry_re_queue_multiple, link_entry_delete_multiple], sources=[DocumentVersionOCRError]) - menu_object.bind_links(links=[link_document_submit], sources=[Document]) - menu_object.bind_links(links=[link_entry_re_queue, link_entry_delete], sources=[DocumentVersionOCRError]) - menu_object.bind_links(links=[link_document_type_ocr_settings], sources=[DocumentType]) - menu_secondary.bind_links(links=[link_entry_list], sources=['ocr:entry_list', 'ocr:entry_delete_multiple', 'ocr:entry_re_queue_multiple', DocumentVersionOCRError]) - menu_tools.bind_links(links=[link_document_submit_all, link_entry_list]) + menu_facet.bind_links( + links=[link_document_content], sources=[Document] + ) + menu_multi_item.bind_links( + links=[link_document_submit_multiple], sources=[Document] + ) + menu_multi_item.bind_links( + links=[link_entry_re_queue_multiple, link_entry_delete_multiple], + sources=[DocumentVersionOCRError] + ) + menu_object.bind_links( + links=[link_document_submit], sources=[Document] + ) + menu_object.bind_links( + links=[link_entry_re_queue, link_entry_delete],\ + sources=[DocumentVersionOCRError] + ) + menu_object.bind_links( + links=[link_document_type_ocr_settings], sources=[DocumentType] + ) + menu_secondary.bind_links( + links=[link_entry_list], + sources=['ocr:entry_list', 'ocr:entry_delete_multiple', 'ocr:entry_re_queue_multiple', DocumentVersionOCRError] + ) + menu_tools.bind_links( + links=[link_document_submit_all, link_entry_list] + ) - post_save.connect(initialize_new_ocr_settings, dispatch_uid='initialize_new_ocr_settings', sender=DocumentType) - post_version_upload.connect(post_version_upload_ocr, dispatch_uid='post_version_upload_ocr', sender=DocumentVersion) + post_save.connect( + initialize_new_ocr_settings, + dispatch_uid='initialize_new_ocr_settings', sender=DocumentType + ) + post_version_upload.connect( + post_version_upload_ocr, dispatch_uid='post_version_upload_ocr', + sender=DocumentVersion + ) namespace = PropertyNamespace('ocr', _('OCR')) try: pdftotext = sh.Command(setting_pdftotext_path.value) except sh.CommandNotFound: - namespace.add_property('pdftotext', _('pdftotext version'), _('not found'), report=True) + namespace.add_property( + 'pdftotext', _('pdftotext version'), _('not found'), + report=True + ) except Exception: - namespace.add_property('pdftotext', _('pdftotext version'), _('error getting version'), report=True) + namespace.add_property( + 'pdftotext', _('pdftotext version'), + _('error getting version'), report=True + ) else: - namespace.add_property('pdftotext', _('pdftotext version'), pdftotext('-v').stderr, report=True) + namespace.add_property( + 'pdftotext', _('pdftotext version'), pdftotext('-v').stderr, + report=True + ) try: tesseract = sh.Command(setting_tesseract_path.value) except sh.CommandNotFound: - namespace.add_property('tesseract', _('tesseract version'), _('not found'), report=True) + namespace.add_property( + 'tesseract', _('tesseract version'), _('not found'), + report=True + ) except Exception: - namespace.add_property('tesseract', _('tesseract version'), _('error getting version'), report=True) + namespace.add_property( + 'tesseract', _('tesseract version'), + _('error getting version'), report=True + ) else: - namespace.add_property('tesseract', _('tesseract version'), tesseract('-v').stderr, report=True) + namespace.add_property( + 'tesseract', _('tesseract version'), tesseract('-v').stderr, + report=True + ) try: unpaper = sh.Command(setting_unpaper_path.value) except sh.CommandNotFound: - namespace.add_property('unpaper', _('unpaper version'), _('not found'), report=True) + namespace.add_property( + 'unpaper', _('unpaper version'), _('not found'), report=True + ) except Exception: - namespace.add_property('unpaper', _('unpaper version'), _('error getting version'), report=True) + namespace.add_property( + 'unpaper', _('unpaper version'), _('error getting version'), + report=True + ) else: - namespace.add_property('unpaper', _('unpaper version'), unpaper('-V').stdout, report=True) + namespace.add_property( + 'unpaper', _('unpaper version'), unpaper('-V').stdout, + report=True + ) diff --git a/mayan/apps/ocr/classes.py b/mayan/apps/ocr/classes.py index 0bc8701fd5..4a1e90c71f 100644 --- a/mayan/apps/ocr/classes.py +++ b/mayan/apps/ocr/classes.py @@ -20,12 +20,20 @@ class OCRBackendBase(object): for page in document_version.pages.all(): image = page.get_image() - logger.info('Processing page: %d of document version: %s', page.page_number, document_version) + logger.info( + 'Processing page: %d of document version: %s', + page.page_number, document_version + ) document_page_content, created = DocumentPageContent.objects.get_or_create(document_page=page) - document_page_content.content = self.execute(file_object=image, language=language) + document_page_content.content = self.execute( + file_object=image, language=language + ) document_page_content.save() image.close() - logger.info('Finished processing page: %d of document version: %s', page.page_number, document_version) + logger.info( + 'Finished processing page: %d of document version: %s', + page.page_number, document_version + ) def execute(self, file_object, language=None, transformations=None): self.language = language diff --git a/mayan/apps/ocr/forms.py b/mayan/apps/ocr/forms.py index 461c0a8579..35af9f92e0 100644 --- a/mayan/apps/ocr/forms.py +++ b/mayan/apps/ocr/forms.py @@ -33,11 +33,22 @@ class DocumentContentForm(forms.Form): pass else: content.append(conditional_escape(force_unicode(page_content))) - content.append('\n\n\n
    - %s -

    \n\n\n' % (ugettext('Page %(page_number)d') % {'page_number': page.page_number})) + content.append( + '\n\n\n
    - %s -

    \n\n\n' % ( + ugettext( + 'Page %(page_number)d' + ) % {'page_number': page.page_number} + ) + ) self.fields['contents'].initial = mark_safe(''.join(content)) contents = forms.CharField( label=_('Contents'), - widget=TextAreaDiv(attrs={'class': 'text_area_div full-height', 'data-height-difference': 360}) + widget=TextAreaDiv( + attrs={ + 'class': 'text_area_div full-height', + 'data-height-difference': 360 + } + ) ) diff --git a/mayan/apps/ocr/links.py b/mayan/apps/ocr/links.py index bd5e845f84..8270f03802 100644 --- a/mayan/apps/ocr/links.py +++ b/mayan/apps/ocr/links.py @@ -9,13 +9,40 @@ from .permissions import ( permission_ocr_document_delete, permission_document_type_ocr_setup ) -link_document_content = Link(permissions=[permission_ocr_content_view], text=_('Content'), view='ocr:document_content', args='resolved_object.id') -link_document_submit = Link(permissions=[permission_ocr_document], text=_('Submit for OCR'), view='ocr:document_submit', args='object.id') -link_document_submit_all = Link(icon='fa fa-font', permissions=[permission_ocr_document], text=_('OCR all documents'), view='ocr:document_submit_all') -link_document_submit_multiple = Link(text=_('Submit for OCR'), view='ocr:document_submit_multiple') -link_document_type_ocr_settings = Link(permissions=[permission_document_type_ocr_setup], text=_('Setup OCR'), view='ocr:document_type_ocr_settings', args='resolved_object.id') -link_entry_delete = Link(permissions=[permission_ocr_document_delete], text=_('Delete'), view='ocr:entry_delete', args='object.id') -link_entry_delete_multiple = Link(text=_('Delete'), view='ocr:entry_delete_multiple') -link_entry_list = Link(icon='fa fa-file-text-o', permissions=[permission_ocr_document], text=_('OCR errors'), view='ocr:entry_list') -link_entry_re_queue = Link(permissions=[permission_ocr_document], text=_('Re-queue'), view='ocr:entry_re_queue', args='object.id') -link_entry_re_queue_multiple = Link(text=_('Re-queue'), view='ocr:entry_re_queue_multiple') +link_document_content = Link( + permissions=[permission_ocr_content_view], text=_('Content'), + view='ocr:document_content', args='resolved_object.id' +) +link_document_submit = Link( + permissions=[permission_ocr_document], text=_('Submit for OCR'), + view='ocr:document_submit', args='object.id' +) +link_document_submit_all = Link( + icon='fa fa-font', permissions=[permission_ocr_document], + text=_('OCR all documents'), view='ocr:document_submit_all' +) +link_document_submit_multiple = Link( + text=_('Submit for OCR'), view='ocr:document_submit_multiple' +) +link_document_type_ocr_settings = Link( + permissions=[permission_document_type_ocr_setup], text=_('Setup OCR'), + view='ocr:document_type_ocr_settings', args='resolved_object.id' +) +link_entry_delete = Link( + permissions=[permission_ocr_document_delete], text=_('Delete'), + view='ocr:entry_delete', args='object.id' +) +link_entry_delete_multiple = Link( + text=_('Delete'), view='ocr:entry_delete_multiple' +) +link_entry_list = Link( + icon='fa fa-file-text-o', permissions=[permission_ocr_document], + text=_('OCR errors'), view='ocr:entry_list' +) +link_entry_re_queue = Link( + permissions=[permission_ocr_document], text=_('Re-queue'), + view='ocr:entry_re_queue', args='object.id' +) +link_entry_re_queue_multiple = Link( + text=_('Re-queue'), view='ocr:entry_re_queue_multiple' +) diff --git a/mayan/apps/ocr/models.py b/mayan/apps/ocr/models.py index 772e801634..8c44d317ca 100644 --- a/mayan/apps/ocr/models.py +++ b/mayan/apps/ocr/models.py @@ -11,8 +11,14 @@ class DocumentTypeSettings(models.Model): """ Define for OCR for a specific document should behave """ - document_type = models.OneToOneField(DocumentType, related_name='ocr_settings', unique=True, verbose_name=_('Document type')) - auto_ocr = models.BooleanField(default=True, verbose_name=_('Automatically queue newly created documents for OCR.')) + document_type = models.OneToOneField( + DocumentType, related_name='ocr_settings', unique=True, + verbose_name=_('Document type') + ) + auto_ocr = models.BooleanField( + default=True, + verbose_name=_('Automatically queue newly created documents for OCR.') + ) class Meta: verbose_name = _('Document type settings') @@ -21,8 +27,12 @@ class DocumentTypeSettings(models.Model): @python_2_unicode_compatible class DocumentVersionOCRError(models.Model): - document_version = models.ForeignKey(DocumentVersion, verbose_name=_('Document version')) - datetime_submitted = models.DateTimeField(auto_now=True, db_index=True, verbose_name=_('Date time submitted')) + document_version = models.ForeignKey( + DocumentVersion, verbose_name=_('Document version') + ) + datetime_submitted = models.DateTimeField( + auto_now=True, db_index=True, verbose_name=_('Date time submitted') + ) result = models.TextField(blank=True, null=True, verbose_name=_('Result')) def __str__(self): @@ -39,7 +49,10 @@ class DocumentPageContent(models.Model): """ Model that describes a document page content """ - document_page = models.OneToOneField(DocumentPage, related_name='ocr_content', verbose_name=_('Document page')) + document_page = models.OneToOneField( + DocumentPage, related_name='ocr_content', + verbose_name=_('Document page') + ) content = models.TextField(blank=True, verbose_name=_('Content')) def __str__(self): diff --git a/mayan/apps/ocr/parsers/__init__.py b/mayan/apps/ocr/parsers/__init__.py index e59572b19d..79795d2fa1 100644 --- a/mayan/apps/ocr/parsers/__init__.py +++ b/mayan/apps/ocr/parsers/__init__.py @@ -24,10 +24,13 @@ def register_parser(mimetypes, parsers): try: parser_instance = parser() except ParserError: - # If parser fails initialization is not added to the list for this mimetype + # If parser fails initialization is not added to the list for + # this mimetype pass else: - mimetype_registry.setdefault(mimetype, []).append(parser_instance) + mimetype_registry.setdefault(mimetype, []).append( + parser_instance + ) def parse_document_page(document_page, descriptor=None, mimetype=None): @@ -66,7 +69,10 @@ class Parser(object): """ def parse(self, document_page, descriptor=None): - raise NotImplementedError('Your %s class has not defined a parse() method, which is required.', self.__class__.__name__) + raise NotImplementedError( + 'Your %s class has not defined a parse() method, which is required.', + self.__class__.__name__ + ) class SlateParser(Parser): @@ -105,11 +111,15 @@ class PopplerParser(Parser): pagenum = str(document_page.page_number) if descriptor: - destination_descriptor, temp_filepath = tempfile.mkstemp(dir=setting_temporary_directory.value) + destination_descriptor, temp_filepath = tempfile.mkstemp( + dir=setting_temporary_directory.value + ) copyfile(descriptor, temp_filepath) document_file = temp_filepath else: - document_file = document_page.document.document_save_to_temp_dir(document_page.document.checksum) + document_file = document_page.document.document_save_to_temp_dir( + document_page.document.checksum + ) logger.debug('document_file: %s', document_file) @@ -124,7 +134,10 @@ class PopplerParser(Parser): command.append(document_file) command.append('-') - proc = subprocess.Popen(command, close_fds=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) + proc = subprocess.Popen( + command, close_fds=True, stderr=subprocess.PIPE, + stdout=subprocess.PIPE + ) return_code = proc.wait() if return_code != 0: logger.error(proc.stderr.readline()) @@ -139,4 +152,6 @@ class PopplerParser(Parser): document_page.save() -register_parser(mimetypes=['application/pdf'], parsers=[PopplerParser, SlateParser]) +register_parser( + mimetypes=['application/pdf'], parsers=[PopplerParser, SlateParser] +) diff --git a/mayan/apps/ocr/permissions.py b/mayan/apps/ocr/permissions.py index 00f2313baf..4f7a0b7e35 100644 --- a/mayan/apps/ocr/permissions.py +++ b/mayan/apps/ocr/permissions.py @@ -6,7 +6,17 @@ from permissions import PermissionNamespace namespace = PermissionNamespace('ocr', _('OCR')) -permission_ocr_document = namespace.add_permission(name='ocr_document', label=_('Submit documents for OCR')) -permission_ocr_document_delete = namespace.add_permission(name='ocr_document_delete', label=_('Delete documents from OCR queue')) -permission_ocr_content_view = namespace.add_permission(name='ocr_content_view', label=_('Can view the transcribed text from document')) -permission_document_type_ocr_setup = namespace.add_permission(name='ocr_document_type_setup', label=_('Change document type OCR settings')) +permission_ocr_document = namespace.add_permission( + name='ocr_document', label=_('Submit documents for OCR') +) +permission_ocr_document_delete = namespace.add_permission( + name='ocr_document_delete', label=_('Delete documents from OCR queue') +) +permission_ocr_content_view = namespace.add_permission( + name='ocr_content_view', + label=_('Can view the transcribed text from document') +) +permission_document_type_ocr_setup = namespace.add_permission( + name='ocr_document_type_setup', + label=_('Change document type OCR settings') +) diff --git a/mayan/apps/ocr/settings.py b/mayan/apps/ocr/settings.py index fb2aaeac2d..d959740a6b 100644 --- a/mayan/apps/ocr/settings.py +++ b/mayan/apps/ocr/settings.py @@ -5,7 +5,20 @@ from django.utils.translation import ugettext_lazy as _ from smart_settings import Namespace namespace = Namespace(name='ocr', label=_('OCR')) -setting_tesseract_path = namespace.add_setting(global_name='OCR_TESSERACT_PATH', default='/usr/bin/tesseract', help_text=_('File path to tesseract program.'), is_path=True) -setting_unpaper_path = namespace.add_setting(global_name='OCR_UNPAPER_PATH', default='/usr/bin/unpaper', help_text=_('File path to unpaper program.'), is_path=True) -setting_pdftotext_path = namespace.add_setting(global_name='OCR_PDFTOTEXT_PATH', default='/usr/bin/pdftotext', help_text=_('File path to poppler\'s pdftotext program used to extract text from PDF files.'), is_path=True) -setting_ocr_backend = namespace.add_setting(global_name='OCR_BACKEND', default='ocr.backends.tesseract.Tesseract', help_text=_('Full path to the backend to be used to do OCR.')) +setting_tesseract_path = namespace.add_setting( + global_name='OCR_TESSERACT_PATH', default='/usr/bin/tesseract', + help_text=_('File path to tesseract program.'), is_path=True +) +setting_unpaper_path = namespace.add_setting( + global_name='OCR_UNPAPER_PATH', default='/usr/bin/unpaper', + help_text=_('File path to unpaper program.'), is_path=True +) +setting_pdftotext_path = namespace.add_setting( + global_name='OCR_PDFTOTEXT_PATH', default='/usr/bin/pdftotext', + help_text=_('File path to poppler\'s pdftotext program used to extract text from PDF files.'), + is_path=True +) +setting_ocr_backend = namespace.add_setting( + global_name='OCR_BACKEND', default='ocr.backends.tesseract.Tesseract', + help_text=_('Full path to the backend to be used to do OCR.') +) diff --git a/mayan/apps/ocr/signals.py b/mayan/apps/ocr/signals.py index bd5a3a90b0..872f7916c6 100644 --- a/mayan/apps/ocr/signals.py +++ b/mayan/apps/ocr/signals.py @@ -2,4 +2,6 @@ from __future__ import unicode_literals from django.dispatch import Signal -post_document_version_ocr = Signal(providing_args=['instance'], use_caching=True) +post_document_version_ocr = Signal( + providing_args=['instance'], use_caching=True +) diff --git a/mayan/apps/ocr/tasks.py b/mayan/apps/ocr/tasks.py index c996459a18..ec8fdee85d 100644 --- a/mayan/apps/ocr/tasks.py +++ b/mayan/apps/ocr/tasks.py @@ -66,4 +66,3 @@ def task_do_ocr(self, document_version_pk): lock.release() except LockError: logger.debug('unable to obtain lock: %s' % lock_id) - pass diff --git a/mayan/apps/ocr/test_models.py b/mayan/apps/ocr/test_models.py index 598112e25b..e2f9fdd467 100644 --- a/mayan/apps/ocr/test_models.py +++ b/mayan/apps/ocr/test_models.py @@ -16,19 +16,25 @@ class DocumentOCRTestCase(TestCase): 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='small document') + self.document = self.document_type.new_document( + file_object=File(file_object), label='small document' + ) def tearDown(self): self.document.delete() self.document_type.delete() def test_ocr_language_backends_end(self): - self.assertTrue('Mayan EDMS Documentation' in self.document.pages.first().ocr_content.content) + self.assertTrue( + 'Mayan EDMS Documentation' in self.document.pages.first().ocr_content.content + ) class GermanOCRSupportTestCase(TestCase): def setUp(self): - self.document_type = DocumentType.objects.create(label=TEST_DOCUMENT_TYPE) + self.document_type = DocumentType.objects.create( + label=TEST_DOCUMENT_TYPE + ) # Get corresponding language code for German from the default language # choices list @@ -37,12 +43,18 @@ class GermanOCRSupportTestCase(TestCase): self.assertEqual('deu', language_code) with open(TEST_DEU_DOCUMENT_PATH) as file_object: - self.document = self.document_type.new_document(file_object=File(file_object), language=language_code) + self.document = self.document_type.new_document( + file_object=File(file_object), language=language_code + ) def tearDown(self): self.document.delete() self.document_type.delete() def test_ocr_language_backends_end(self): - self.assertTrue('Repository für elektronische Dokumente.' in self.document.pages.first().ocr_content.content) - self.assertTrue('Es bietet einen elektronischen Tresor oder' in self.document.pages.first().ocr_content.content) + self.assertTrue( + 'Repository für elektronische Dokumente.' in self.document.pages.first().ocr_content.content + ) + self.assertTrue( + 'Es bietet einen elektronischen Tresor oder' in self.document.pages.first().ocr_content.content + ) diff --git a/mayan/apps/ocr/urls.py b/mayan/apps/ocr/urls.py index bc598f5ddb..ee9b74e25f 100644 --- a/mayan/apps/ocr/urls.py +++ b/mayan/apps/ocr/urls.py @@ -10,20 +10,45 @@ from .views import ( urlpatterns = patterns( 'ocr.views', - url(r'^(?P\d+)/content/$', 'document_content', name='document_content'), - url(r'^document/(?P\d+)/submit/$', DocumentSubmitView.as_view(), name='document_submit'), - url(r'^document/all/submit/$', DocumentAllSubmitView.as_view(), name='document_submit_all'), - url(r'^document/multiple/submit/$', DocumentManySubmitView.as_view(), name='document_submit_multiple'), - url(r'^document_type/(?P\d+)/ocr/settings/$', DocumentTypeSettingsEditView.as_view(), name='document_type_ocr_settings'), + url( + r'^(?P\d+)/content/$', 'document_content', + name='document_content' + ), + url( + r'^document/(?P\d+)/submit/$', DocumentSubmitView.as_view(), + name='document_submit' + ), + url( + r'^document/all/submit/$', DocumentAllSubmitView.as_view(), + name='document_submit_all' + ), + url( + r'^document/multiple/submit/$', DocumentManySubmitView.as_view(), + name='document_submit_multiple' + ), + url( + r'^document_type/(?P\d+)/ocr/settings/$', + DocumentTypeSettingsEditView.as_view(), + name='document_type_ocr_settings' + ), url(r'^all/$', 'entry_list', name='entry_list'), url(r'^(?P\d+)/delete/$', 'entry_delete', name='entry_delete'), - url(r'^multiple/delete/$', 'entry_delete_multiple', name='entry_delete_multiple'), + url( + r'^multiple/delete/$', 'entry_delete_multiple', + name='entry_delete_multiple' + ), url(r'^(?P\d+)/re-queue/$', 'entry_re_queue', name='entry_re_queue'), - url(r'^multiple/re-queue/$', 'entry_re_queue_multiple', name='entry_re_queue_multiple'), + url( + r'^multiple/re-queue/$', 'entry_re_queue_multiple', + name='entry_re_queue_multiple' + ), ) api_urls = patterns( '', - url(r'^submit/$', DocumentVersionOCRView.as_view(), name='document-version-ocr-submit-view'), + url( + r'^submit/$', DocumentVersionOCRView.as_view(), + name='document-version-ocr-submit-view' + ), ) diff --git a/mayan/apps/ocr/views.py b/mayan/apps/ocr/views.py index 487b7c9701..d52ba45f9a 100644 --- a/mayan/apps/ocr/views.py +++ b/mayan/apps/ocr/views.py @@ -33,13 +33,20 @@ class DocumentSubmitView(ConfirmView): document = obj try: - Permission.check_permissions(request.user, [permission_ocr_document]) + Permission.check_permissions( + request.user, [permission_ocr_document] + ) except PermissionDenied: - AccessControlList.objects.check_access(permission_ocr_document, request.user, document) + AccessControlList.objects.check_access( + permission_ocr_document, request.user, document + ) document.submit_for_ocr() - messages.success(request, _('Document: %(document)s was added to the OCR queue.') % { - 'document': document} + messages.success( + request, + _('Document: %(document)s was added to the OCR queue.') % { + 'document': document + } ) def post(self, request, *args, **kwargs): @@ -58,7 +65,9 @@ class DocumentAllSubmitView(ConfirmView): document.submit_for_ocr() count += 1 - messages.success(request, _('%d documents added to the OCR queue.') % count) + messages.success( + request, _('%d documents added to the OCR queue.') % count + ) return HttpResponseRedirect(self.get_success_url()) @@ -82,14 +91,20 @@ class DocumentTypeSettingsEditView(SingleObjectEditView): view_permission = permission_document_type_ocr_setup def get_object(self, queryset=None): - return get_object_or_404(DocumentType, pk=self.kwargs['pk']).ocr_settings + return get_object_or_404( + DocumentType, pk=self.kwargs['pk'] + ).ocr_settings def get_context_data(self, **kwargs): - context = super(DocumentTypeSettingsEditView, self).get_context_data(**kwargs) + context = super( + DocumentTypeSettingsEditView, self + ).get_context_data(**kwargs) context.update( { - 'title': _('Edit OCR settings for document type: %s') % self.get_object().document_type + 'title': _( + 'Edit OCR settings for document type: %s' + ) % self.get_object().document_type } ) @@ -100,9 +115,13 @@ def document_content(request, document_id): document = get_object_or_404(Document, pk=document_id) try: - Permission.check_permissions(request.user, [permission_ocr_content_view]) + Permission.check_permissions( + request.user, [permission_ocr_content_view] + ) except PermissionDenied: - AccessControlList.objects.check_access(permission_ocr_content_view, request.user, document) + AccessControlList.objects.check_access( + permission_ocr_content_view, request.user, document + ) document.add_as_recent_document_for_user(request.user) @@ -132,7 +151,9 @@ def entry_list(request): def entry_delete(request, pk=None, pk_list=None): - Permission.check_permissions(request.user, [permission_ocr_document_delete]) + Permission.check_permissions( + request.user, [permission_ocr_document_delete] + ) if pk: entries = [get_object_or_404(DocumentVersionOCRError, pk=pk)] @@ -140,7 +161,11 @@ def entry_delete(request, pk=None, pk_list=None): entries = [get_object_or_404(DocumentVersionOCRError, pk=pk) for pk in pk_list.split(',')] else: messages.error(request, _('Make at least one selection.')) - return HttpResponseRedirect(request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))) + return HttpResponseRedirect( + 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)))) @@ -149,12 +174,18 @@ def entry_delete(request, pk=None, pk_list=None): for entry in entries: try: entry.delete() - messages.success(request, _('Entry: %(entry)s deleted successfully.') % { - 'entry': entry}) + messages.success( + request, _('Entry: %(entry)s deleted successfully.') % { + 'entry': entry + } + ) except Exception as exception: - messages.error(request, _('Error entry: %(entry)s; %(error)s') % { - 'entry': entry, 'error': exception}) + messages.error( + request, _('Error entry: %(entry)s; %(error)s') % { + 'entry': entry, 'error': exception + } + ) return HttpResponseRedirect(next) context = { diff --git a/mayan/apps/permissions/apps.py b/mayan/apps/permissions/apps.py index 25e7d758ef..b823e8c41c 100644 --- a/mayan/apps/permissions/apps.py +++ b/mayan/apps/permissions/apps.py @@ -26,9 +26,22 @@ class PermissionsApp(MayanAppConfig): APIEndPoint('permissions') - menu_object.bind_links(links=[link_role_edit, link_role_members, link_role_permissions, link_role_delete], sources=[Role]) - menu_multi_item.bind_links(links=[link_permission_grant, link_permission_revoke], sources=['permissions:role_permissions']) - menu_secondary.bind_links(links=[link_role_list, link_role_create], sources=[Role, 'permissions:role_create', 'permissions:role_list']) + menu_object.bind_links( + links=[ + link_role_edit, link_role_members, link_role_permissions, + link_role_delete + ], sources=[Role] + ) + menu_multi_item.bind_links( + links=[link_permission_grant, link_permission_revoke], + sources=['permissions:role_permissions'] + ) + menu_secondary.bind_links( + links=[link_role_list, link_role_create], + sources=[Role, 'permissions:role_create', 'permissions:role_list'] + ) menu_setup.bind_links(links=[link_role_list]) - perform_upgrade.connect(purge_permissions, dispatch_uid='purge_permissions') + perform_upgrade.connect( + purge_permissions, dispatch_uid='purge_permissions' + ) diff --git a/mayan/apps/permissions/classes.py b/mayan/apps/permissions/classes.py index 3fcd26286b..30ae8958c3 100644 --- a/mayan/apps/permissions/classes.py +++ b/mayan/apps/permissions/classes.py @@ -23,7 +23,9 @@ class PermissionNamespace(object): try: return cls._registry[name] except KeyError: - raise InvalidNamespace('Invalid namespace name. This is probably an obsolete permission namespace, execute the management command "purge_permissions" and try again.') + raise InvalidNamespace( + 'Invalid namespace name. This is probably an obsolete permission namespace, execute the management command "purge_permissions" and try again.' + ) def __init__(self, name, label): self.name = name @@ -65,7 +67,9 @@ class Permission(object): @classmethod def all(cls): # Return sorted permisions by namespace.name - return sorted(cls._permissions.values(), key=lambda x: x.namespace.name) + return sorted( + cls._permissions.values(), key=lambda x: x.namespace.name + ) @classmethod def get(cls, get_dict, proxy_only=False): diff --git a/mayan/apps/permissions/links.py b/mayan/apps/permissions/links.py index b8748d482f..142d286a0a 100644 --- a/mayan/apps/permissions/links.py +++ b/mayan/apps/permissions/links.py @@ -10,11 +10,36 @@ from .permissions import ( permission_role_view ) -link_permission_grant = Link(permissions=[permission_permission_grant], text=_('Grant'), view='permissions:permission_multiple_grant') -link_permission_revoke = Link(permissions=[permission_permission_revoke], text=_('Revoke'), view='permissions:permission_multiple_revoke') -link_role_create = Link(permissions=[permission_role_create], text=_('Create new role'), view='permissions:role_create') -link_role_delete = Link(permissions=[permission_role_delete], tags='dangerous', text=_('Delete'), view='permissions:role_delete', args='object.id') -link_role_edit = Link(permissions=[permission_role_edit], text=_('Edit'), view='permissions:role_edit', args='object.id') -link_role_list = Link(icon='fa fa-user-secret', permissions=[permission_role_view], text=_('Roles'), view='permissions:role_list') -link_role_members = Link(permissions=[permission_role_edit], text=_('Members'), view='permissions:role_members', args='object.id') -link_role_permissions = Link(permissions=[permission_permission_grant, permission_permission_revoke], text=_('Role permissions'), view='permissions:role_permissions', args='object.id') +link_permission_grant = Link( + permissions=[permission_permission_grant], text=_('Grant'), + view='permissions:permission_multiple_grant' +) +link_permission_revoke = Link( + permissions=[permission_permission_revoke], text=_('Revoke'), + view='permissions:permission_multiple_revoke' +) +link_role_create = Link( + permissions=[permission_role_create], text=_('Create new role'), + view='permissions:role_create' +) +link_role_delete = Link( + permissions=[permission_role_delete], tags='dangerous', text=_('Delete'), + view='permissions:role_delete', args='object.id' +) +link_role_edit = Link( + permissions=[permission_role_edit], text=_('Edit'), + view='permissions:role_edit', args='object.id' +) +link_role_list = Link( + icon='fa fa-user-secret', permissions=[permission_role_view], + text=_('Roles'), view='permissions:role_list' +) +link_role_members = Link( + permissions=[permission_role_edit], text=_('Members'), + view='permissions:role_members', args='object.id' +) +link_role_permissions = Link( + permissions=[permission_permission_grant, permission_permission_revoke], + text=_('Role permissions'), view='permissions:role_permissions', + args='object.id' +) diff --git a/mayan/apps/permissions/management/commands/purgepermissions.py b/mayan/apps/permissions/management/commands/purgepermissions.py index b472ea1c0f..23d36bb3be 100644 --- a/mayan/apps/permissions/management/commands/purgepermissions.py +++ b/mayan/apps/permissions/management/commands/purgepermissions.py @@ -12,6 +12,9 @@ class Command(BaseCommand): def handle(self, *args, **options): for permission in StoredPermission.objects.all(): try: - Permission.get({'pk': '%s.%s' % (permission.namespace, permission.name)}, proxy_only=True) + Permission.get( + {'pk': '%s.%s' % (permission.namespace, permission.name)}, + proxy_only=True + ) except KeyError: permission.delete() diff --git a/mayan/apps/permissions/managers.py b/mayan/apps/permissions/managers.py index 0e92b792a4..cdf36264a6 100644 --- a/mayan/apps/permissions/managers.py +++ b/mayan/apps/permissions/managers.py @@ -11,4 +11,6 @@ logger = logging.getLogger(__name__) class StoredPermissionManager(models.Manager): def get_for_holder(self, holder): ct = ContentType.objects.get_for_model(holder) - return self.model.objects.filter(permissionholder__holder_type=ct).filter(permissionholder__holder_id=holder.pk) + return self.model.objects.filter( + permissionholder__holder_type=ct + ).filter(permissionholder__holder_id=holder.pk) diff --git a/mayan/apps/permissions/models.py b/mayan/apps/permissions/models.py index b81bd2a4b5..26b5d39a17 100644 --- a/mayan/apps/permissions/models.py +++ b/mayan/apps/permissions/models.py @@ -31,7 +31,10 @@ class StoredPermission(models.Model): super(StoredPermission, self).__init__(*args, **kwargs) try: - self.volatile_permission = Permission.get({'pk': '%s.%s' % (self.namespace, self.name)}, proxy_only=True) + self.volatile_permission = Permission.get( + {'pk': '%s.%s' % (self.namespace, self.name)}, + proxy_only=True + ) except KeyError: # Must be a deprecated permission in the database that is no # longer used in the current code @@ -57,9 +60,15 @@ class StoredPermission(models.Model): @python_2_unicode_compatible class Role(models.Model): - label = models.CharField(max_length=64, unique=True, verbose_name=_('Label')) - permissions = models.ManyToManyField(StoredPermission, related_name='roles', verbose_name=_('Permissions')) - groups = models.ManyToManyField(Group, related_name='roles', verbose_name=_('Groups')) + label = models.CharField( + max_length=64, unique=True, verbose_name=_('Label') + ) + permissions = models.ManyToManyField( + StoredPermission, related_name='roles', verbose_name=_('Permissions') + ) + groups = models.ManyToManyField( + Group, related_name='roles', verbose_name=_('Groups') + ) class Meta: ordering = ('label',) diff --git a/mayan/apps/permissions/permissions.py b/mayan/apps/permissions/permissions.py index 37b63a414f..3d1ee0c371 100644 --- a/mayan/apps/permissions/permissions.py +++ b/mayan/apps/permissions/permissions.py @@ -6,9 +6,21 @@ from . import PermissionNamespace namespace = PermissionNamespace('permissions', _('Permissions')) -permission_role_view = namespace.add_permission(name='role_view', label=_('View roles')) -permission_role_edit = namespace.add_permission(name='role_edit', label=_('Edit roles')) -permission_role_create = namespace.add_permission(name='role_create', label=_('Create roles')) -permission_role_delete = namespace.add_permission(name='role_delete', label=_('Delete roles')) -permission_permission_grant = namespace.add_permission(name='permission_grant', label=_('Grant permissions')) -permission_permission_revoke = namespace.add_permission(name='permission_revoke', label=_('Revoke permissions')) +permission_role_view = namespace.add_permission( + name='role_view', label=_('View roles') +) +permission_role_edit = namespace.add_permission( + name='role_edit', label=_('Edit roles') +) +permission_role_create = namespace.add_permission( + name='role_create', label=_('Create roles') +) +permission_role_delete = namespace.add_permission( + name='role_delete', label=_('Delete roles') +) +permission_permission_grant = namespace.add_permission( + name='permission_grant', label=_('Grant permissions') +) +permission_permission_revoke = namespace.add_permission( + name='permission_revoke', label=_('Revoke permissions') +) diff --git a/mayan/apps/permissions/test_models.py b/mayan/apps/permissions/test_models.py index f084bfb558..7b6edbe07d 100644 --- a/mayan/apps/permissions/test_models.py +++ b/mayan/apps/permissions/test_models.py @@ -24,7 +24,9 @@ class PermissionTestCase(TestCase): def test_no_permissions(self): with self.assertRaises(PermissionDenied): - Permission.check_permissions(requester=self.user, permissions=(permission_role_view,)) + Permission.check_permissions( + requester=self.user, permissions=(permission_role_view,) + ) def test_with_permissions(self): self.group.user_set.add(self.user) @@ -32,6 +34,8 @@ class PermissionTestCase(TestCase): self.role.groups.add(self.group) try: - Permission.check_permissions(requester=self.user, permissions=(permission_role_view,)) + Permission.check_permissions( + requester=self.user, permissions=(permission_role_view,) + ) except PermissionDenied: self.fail('PermissionDenied exception was not expected.') diff --git a/mayan/apps/permissions/urls.py b/mayan/apps/permissions/urls.py index 79287170b9..2f8be876d8 100644 --- a/mayan/apps/permissions/urls.py +++ b/mayan/apps/permissions/urls.py @@ -12,10 +12,19 @@ urlpatterns = patterns( 'permissions.views', url(r'^role/list/$', RoleListView.as_view(), name='role_list'), url(r'^role/create/$', RoleCreateView.as_view(), name='role_create'), - url(r'^role/(?P\d+)/permissions/$', SetupRolePermissionsView.as_view(), name='role_permissions'), + url( + r'^role/(?P\d+)/permissions/$', SetupRolePermissionsView.as_view(), + name='role_permissions' + ), url(r'^role/(?P\d+)/edit/$', RoleEditView.as_view(), name='role_edit'), - url(r'^role/(?P\d+)/delete/$', RoleDeleteView.as_view(), name='role_delete'), - url(r'^role/(?P\d+)/members/$', SetupRoleMembersView.as_view(), name='role_members'), + url( + r'^role/(?P\d+)/delete/$', RoleDeleteView.as_view(), + name='role_delete' + ), + url( + r'^role/(?P\d+)/members/$', SetupRoleMembersView.as_view(), + name='role_members' + ), ) api_urls = patterns( diff --git a/mayan/apps/permissions/views.py b/mayan/apps/permissions/views.py index 46ff91e788..b061fa8ecd 100644 --- a/mayan/apps/permissions/views.py +++ b/mayan/apps/permissions/views.py @@ -54,10 +54,14 @@ class SetupRoleMembersView(AssignRemoveView): return get_object_or_404(Role, pk=self.kwargs['pk']) def left_list(self): - return [(unicode(group.pk), group.name) for group in set(Group.objects.all()) - set(self.get_object().groups.all())] + return [ + (unicode(group.pk), group.name) for group in set(Group.objects.all()) - set(self.get_object().groups.all()) + ] def right_list(self): - return [(unicode(group.pk), group.name) for group in self.get_object().groups.all()] + return [ + (unicode(group.pk), group.name) for group in self.get_object().groups.all() + ] def remove(self, item): group = get_object_or_404(Group, pk=item) @@ -80,7 +84,9 @@ class SetupRolePermissionsView(AssignRemoveView): view_permission = permission_role_view def add(self, item): - Permission.check_permissions(self.request.user, permissions=(permission_permission_grant,)) + Permission.check_permissions( + self.request.user, permissions=(permission_permission_grant,) + ) permission = get_object_or_404(StoredPermission, pk=item) self.get_object().permissions.add(permission) @@ -90,21 +96,31 @@ class SetupRolePermissionsView(AssignRemoveView): def left_list(self): results = [] for namespace, permissions in itertools.groupby(StoredPermission.objects.exclude(id__in=self.get_object().permissions.values_list('pk', flat=True)), 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 def right_list(self): results = [] for namespace, permissions in itertools.groupby(self.get_object().permissions.all(), 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 def remove(self, item): - Permission.check_permissions(self.request.user, permissions=(permission_permission_revoke,)) + Permission.check_permissions( + self.request.user, permissions=(permission_permission_revoke,) + ) permission = get_object_or_404(StoredPermission, pk=item) self.get_object().permissions.remove(permission) diff --git a/mayan/apps/rest_api/classes.py b/mayan/apps/rest_api/classes.py index aca7f36b40..c34dbf8e3f 100644 --- a/mayan/apps/rest_api/classes.py +++ b/mayan/apps/rest_api/classes.py @@ -23,7 +23,9 @@ class APIEndPoint(object): self.name = name self.endpoints = [] try: - api_urls = import_string('{0}.urls.api_urls'.format(app_name or name)) + api_urls = import_string( + '{0}.urls.api_urls'.format(app_name or name) + ) except Exception: if settings.DEBUG: raise diff --git a/mayan/apps/rest_api/filters.py b/mayan/apps/rest_api/filters.py index fe718f2999..aee7c7948f 100644 --- a/mayan/apps/rest_api/filters.py +++ b/mayan/apps/rest_api/filters.py @@ -10,13 +10,17 @@ 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) + required_permission = getattr( + view, 'mayan_object_permissions', {}).get(request.method, None + ) if required_permission: try: Permission.check_permissions(request.user, required_permission) except PermissionDenied: - return AccessControlList.objects.filter_by_access(required_permission[0], request.user, queryset) + return AccessControlList.objects.filter_by_access( + required_permission[0], request.user, queryset + ) else: return queryset else: diff --git a/mayan/apps/rest_api/links.py b/mayan/apps/rest_api/links.py index 50ca3c7818..26f1e905f8 100644 --- a/mayan/apps/rest_api/links.py +++ b/mayan/apps/rest_api/links.py @@ -5,4 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from navigation import Link link_api = Link(icon='fa fa-plug', text=_('REST API'), view='api-root') -link_api_documentation = Link(icon='fa fa-book', text=_('API Documentation'), view='django.swagger.base.view') +link_api_documentation = Link( + icon='fa fa-book', text=_('API Documentation'), + view='django.swagger.base.view' +) diff --git a/mayan/apps/rest_api/permissions.py b/mayan/apps/rest_api/permissions.py index c0e46c9890..0e13979dab 100644 --- a/mayan/apps/rest_api/permissions.py +++ b/mayan/apps/rest_api/permissions.py @@ -12,7 +12,9 @@ from permissions import Permission class MayanPermission(BasePermission): def has_permission(self, request, view): - required_permission = getattr(view, 'mayan_view_permissions', {}).get(request.method, None) + required_permission = getattr( + view, 'mayan_view_permissions', {}).get(request.method, None + ) if required_permission: try: @@ -25,7 +27,9 @@ class MayanPermission(BasePermission): return True def has_object_permission(self, request, view, obj): - required_permission = getattr(view, 'mayan_object_permissions', {}).get(request.method, None) + required_permission = getattr( + view, 'mayan_object_permissions', {}).get(request.method, None + ) if required_permission: try: @@ -33,9 +37,15 @@ class MayanPermission(BasePermission): except PermissionDenied: try: if hasattr(view, 'mayan_permission_attribute_check'): - AccessControlList.objects.check_access(required_permission, request.user, getattr(obj, view.mayan_permission_attribute_check)) + AccessControlList.objects.check_access( + required_permission, request.user, getattr( + obj, view.mayan_permission_attribute_check + ) + ) else: - AccessControlList.objects.check_access(required_permission, request.user, obj) + AccessControlList.objects.check_access( + required_permission, request.user, obj + ) except PermissionDenied: return False else: diff --git a/mayan/apps/rest_api/urls.py b/mayan/apps/rest_api/urls.py index 19c12f5688..82ab9fef65 100644 --- a/mayan/apps/rest_api/urls.py +++ b/mayan/apps/rest_api/urls.py @@ -7,7 +7,9 @@ from .views import APIBase, Version_0, APIAppView, BrowseableObtainAuthToken version_0_urlpatterns = patterns( '', url(r'^$', Version_0.as_view(), name='api-version-0'), - url(r'^(?P\w+)/$', APIAppView.as_view(), name='api-version-0-app'), + url( + r'^(?P\w+)/$', APIAppView.as_view(), name='api-version-0-app' + ), ) urlpatterns = patterns( @@ -18,5 +20,8 @@ urlpatterns = patterns( api_urls = patterns( '', - url(r'^auth/token/obtain/', BrowseableObtainAuthToken.as_view(), name='auth_token_obtain'), + url( + r'^auth/token/obtain/', BrowseableObtainAuthToken.as_view(), + name='auth_token_obtain' + ), ) diff --git a/mayan/apps/rest_api/views.py b/mayan/apps/rest_api/views.py index da4e89f1eb..22a18b5994 100644 --- a/mayan/apps/rest_api/views.py +++ b/mayan/apps/rest_api/views.py @@ -25,7 +25,11 @@ class APIBase(generics.GenericAPIView): def get(self, request, format=None): return Response([ - {'name': 'Version 0', 'url': reverse('api-version-0', request=request, format=format)} + { + 'name': 'Version 0', 'url': reverse( + 'api-version-0', request=request, format=format + ) + } ]) @@ -39,7 +43,11 @@ class Version_0(generics.GenericAPIView): def get(self, request, format=None): return Response({ 'apps': [ - {'name': unicode(endpoint), 'url': reverse('api-version-0-app', args=[unicode(endpoint)], request=request, format=format)} for endpoint in APIEndPoint.get_all() + { + 'name': unicode(endpoint), + 'url': reverse('api-version-0-app', + args=[unicode(endpoint)], request=request, format=format) + } for endpoint in APIEndPoint.get_all() ], }) @@ -54,10 +62,15 @@ class APIAppView(generics.GenericAPIView): def get(self, request, app_name, format=None): api_app = APIEndPoint.get(app_name) - return Response({ - 'name': api_app.name, - 'url': reverse('api-version-0-app', args=[unicode(api_app.name)], request=request, format=format) - }) + return Response( + { + 'name': api_app.name, + 'url': reverse( + 'api-version-0-app', args=[unicode(api_app.name)], + request=request, format=format + ) + } + ) class BrowseableObtainAuthToken(ObtainAuthToken): diff --git a/mayan/apps/smart_settings/apps.py b/mayan/apps/smart_settings/apps.py index a956d1fcfa..d574a8c2e4 100644 --- a/mayan/apps/smart_settings/apps.py +++ b/mayan/apps/smart_settings/apps.py @@ -27,12 +27,25 @@ class SmartSettingsApp(MayanAppConfig): def ready(self): super(SmartSettingsApp, self).ready() - SourceColumn(source=Namespace, label=_('Setting count'), attribute=encapsulate(lambda instance: len(instance.settings))) - SourceColumn(source=Setting, label=_('Name'), attribute=encapsulate(lambda instance: setting_widget(instance))) - SourceColumn(source=Setting, label=_('Value'), attribute='serialized_value') - SourceColumn(source=Setting, label=_('Found in path'), attribute=encapsulate(lambda instance: exists_widget(instance.value) if instance.is_path else _('n/a'))) + SourceColumn( + source=Namespace, label=_('Setting count'), + attribute=encapsulate(lambda instance: len(instance.settings)) + ) + SourceColumn( + source=Setting, label=_('Name'), + attribute=encapsulate(lambda instance: setting_widget(instance)) + ) + SourceColumn( + source=Setting, label=_('Value'), attribute='serialized_value' + ) + SourceColumn( + source=Setting, label=_('Found in path'), + attribute=encapsulate(lambda instance: exists_widget(instance.value) if instance.is_path else _('n/a')) + ) - menu_object.bind_links(links=(link_namespace_detail,), sources=(Namespace,)) + menu_object.bind_links( + links=(link_namespace_detail,), sources=(Namespace,) + ) menu_setup.bind_links(links=(link_namespace_list,)) for app in apps.get_app_configs(): diff --git a/mayan/apps/smart_settings/classes.py b/mayan/apps/smart_settings/classes.py index cfbdcc68b9..b1f0d14ac6 100644 --- a/mayan/apps/smart_settings/classes.py +++ b/mayan/apps/smart_settings/classes.py @@ -23,7 +23,9 @@ class Namespace(object): def __init__(self, name, label): if name in self.__class__._registry: - raise Exception('Namespace names must be unique; "%s" already exists.' % name) + raise Exception( + 'Namespace names must be unique; "%s" already exists.' % name + ) self.name = name self.label = label self.__class__._registry[name] = self @@ -59,7 +61,9 @@ class Setting(object): @property def serialized_value(self): if not self.yaml: - self.yaml = Setting.serialize_value(getattr(settings, self.global_name, self.default)) + self.yaml = Setting.serialize_value( + getattr(settings, self.global_name, self.default) + ) return self.yaml diff --git a/mayan/apps/smart_settings/links.py b/mayan/apps/smart_settings/links.py index bc7fcb3466..81ec4d0832 100644 --- a/mayan/apps/smart_settings/links.py +++ b/mayan/apps/smart_settings/links.py @@ -9,5 +9,11 @@ def is_superuser(context): return context['request'].user.is_staff or context['request'].user.is_superuser -link_namespace_list = Link(condition=is_superuser, icon='fa fa-sliders', text=_('Settings'), view='settings:namespace_list') -link_namespace_detail = Link(condition=is_superuser, text=_('Settings'), view='settings:namespace_detail', args='resolved_object.name') +link_namespace_list = Link( + condition=is_superuser, icon='fa fa-sliders', text=_('Settings'), + view='settings:namespace_list' +) +link_namespace_detail = Link( + condition=is_superuser, text=_('Settings'), + view='settings:namespace_detail', args='resolved_object.name' +) diff --git a/mayan/apps/smart_settings/urls.py b/mayan/apps/smart_settings/urls.py index 49087940b6..9899863360 100644 --- a/mayan/apps/smart_settings/urls.py +++ b/mayan/apps/smart_settings/urls.py @@ -6,6 +6,11 @@ from .views import NamespaceDetailView, NamespaceListView urlpatterns = patterns( '', - url(r'^namespace/all/$', NamespaceListView.as_view(), name='namespace_list'), - url(r'^namespace/(?P\w+)/$', NamespaceDetailView.as_view(), name='namespace_detail'), + url( + r'^namespace/all/$', NamespaceListView.as_view(), name='namespace_list' + ), + url( + r'^namespace/(?P\w+)/$', NamespaceDetailView.as_view(), + name='namespace_detail' + ), ) diff --git a/mayan/apps/sources/api_views.py b/mayan/apps/sources/api_views.py index cda64b4a69..c825a4bae2 100644 --- a/mayan/apps/sources/api_views.py +++ b/mayan/apps/sources/api_views.py @@ -23,8 +23,15 @@ class APIStagingSourceFileView(generics.GenericAPIView): serializer_class = StagingFolderFileSerializer def get(self, request, staging_folder_pk, encoded_filename): - staging_folder = get_object_or_404(StagingFolderSource, pk=staging_folder_pk) - return Response(StagingFolderFileSerializer(staging_folder.get_file(encoded_filename=encoded_filename), context={'request': request}).data) + staging_folder = get_object_or_404( + StagingFolderSource, pk=staging_folder_pk + ) + return Response( + StagingFolderFileSerializer( + staging_folder.get_file(encoded_filename=encoded_filename), + context={'request': request} + ).data + ) class APIStagingSourceListView(generics.ListAPIView): @@ -55,17 +62,36 @@ class APIStagingSourceFileImageView(generics.GenericAPIView): serializer_class = StagingSourceFileImageSerializer def get(self, request, staging_folder_pk, encoded_filename): - staging_folder = get_object_or_404(StagingFolderSource, pk=staging_folder_pk) - staging_file = staging_folder.get_file(encoded_filename=encoded_filename) + staging_folder = get_object_or_404( + StagingFolderSource, pk=staging_folder_pk + ) + staging_file = staging_folder.get_file( + encoded_filename=encoded_filename + ) size = request.GET.get('size', setting_display_size.value) try: return Response({ 'status': 'success', - 'data': staging_file.get_image(as_base64=True, size=size, transformations=Transformation.objects.get_for_model(staging_folder, as_classes=True)) + 'data': staging_file.get_image( + as_base64=True, size=size, + transformations=Transformation.objects.get_for_model( + staging_folder, as_classes=True + ) + ) }) except UnknownFileFormat as exception: - return Response({'status': 'error', 'detail': 'unknown_file_format', 'message': unicode(exception)}) + return Response( + { + 'status': 'error', 'detail': 'unknown_file_format', + 'message': unicode(exception) + } + ) except UnkownConvertError as exception: - return Response({'status': 'error', 'detail': 'converter_error', 'message': unicode(exception)}) + return Response( + { + 'status': 'error', 'detail': 'converter_error', + 'message': unicode(exception) + } + ) diff --git a/mayan/apps/sources/apps.py b/mayan/apps/sources/apps.py index 6f0c315a9b..8a60c4c892 100644 --- a/mayan/apps/sources/apps.py +++ b/mayan/apps/sources/apps.py @@ -47,14 +47,34 @@ class SourcesApp(MayanAppConfig): APIEndPoint('sources') - 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.'), condition=lambda: not Source.objects.exists(), view='sources:setup_source_list') + 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.'), + condition=lambda: not Source.objects.exists(), + view='sources:setup_source_list' + ) - SourceColumn(source=StagingFile, label=_('Thumbnail'), attribute=encapsulate(lambda staging_file: staging_file_thumbnail(staging_file, gallery_name='sources:staging_list', title=staging_file.filename, size='100'))) + SourceColumn( + source=StagingFile, + label=_('Thumbnail'), + attribute=encapsulate( + lambda staging_file: staging_file_thumbnail( + staging_file, + gallery_name='sources:staging_list', + title=staging_file.filename, size='100' + ) + ) + ) app.conf.CELERY_QUEUES.extend( ( - Queue('sources', Exchange('sources'), routing_key='sources'), - Queue('sources_periodic', Exchange('sources_periodic'), routing_key='sources_periodic', delivery_mode=1), + Queue( + 'sources', Exchange('sources'), routing_key='sources' + ), + Queue( + 'sources_periodic', Exchange('sources_periodic'), + routing_key='sources_periodic', delivery_mode=1 + ), ) ) @@ -73,13 +93,51 @@ class SourcesApp(MayanAppConfig): ) menu_front_page.bind_links(links=[link_document_create_multiple]) - menu_object.bind_links(links=[link_document_create_siblings], sources=[Document]) - menu_object.bind_links(links=[link_setup_source_edit, link_setup_source_delete, link_transformation_list, link_setup_source_logs], sources=[POP3Email, IMAPEmail, StagingFolderSource, WatchFolderSource, WebFormSource]) - menu_object.bind_links(links=[link_staging_file_delete], sources=[StagingFile]) - menu_secondary.bind_links(links=[link_setup_sources, link_setup_source_create_webform, link_setup_source_create_staging_folder, link_setup_source_create_pop3_email, link_setup_source_create_imap_email, link_setup_source_create_watch_folder], sources=[POP3Email, IMAPEmail, StagingFolderSource, WatchFolderSource, WebFormSource, 'sources:setup_source_list', 'sources:setup_source_create']) + menu_object.bind_links( + links=[link_document_create_siblings], sources=[Document] + ) + menu_object.bind_links( + links=[ + link_setup_source_edit, link_setup_source_delete, + link_transformation_list, link_setup_source_logs + ], sources=[ + POP3Email, IMAPEmail, StagingFolderSource, WatchFolderSource, + WebFormSource + ] + ) + menu_object.bind_links( + links=[link_staging_file_delete], sources=[StagingFile] + ) + menu_secondary.bind_links( + links=[ + link_setup_sources, link_setup_source_create_webform, + link_setup_source_create_staging_folder, + link_setup_source_create_pop3_email, + link_setup_source_create_imap_email, + link_setup_source_create_watch_folder + ], sources=[ + POP3Email, IMAPEmail, StagingFolderSource, WatchFolderSource, + WebFormSource, 'sources:setup_source_list', + 'sources:setup_source_create' + ] + ) menu_setup.bind_links(links=[link_setup_sources]) - menu_sidebar.bind_links(links=[link_upload_version], sources=['documents:document_version_list', 'documents:upload_version', 'documents:document_version_revert']) + menu_sidebar.bind_links( + links=[link_upload_version], + sources=[ + 'documents:document_version_list', 'documents:upload_version', + 'documents:document_version_revert' + ] + ) - post_upgrade.connect(initialize_periodic_tasks, dispatch_uid='initialize_periodic_tasks') - post_initial_setup.connect(create_default_document_source, dispatch_uid='create_default_document_source') - post_version_upload.connect(copy_transformations_to_version, dispatch_uid='copy_transformations_to_version') + post_upgrade.connect( + initialize_periodic_tasks, dispatch_uid='initialize_periodic_tasks' + ) + post_initial_setup.connect( + create_default_document_source, + dispatch_uid='create_default_document_source' + ) + post_version_upload.connect( + copy_transformations_to_version, + dispatch_uid='copy_transformations_to_version' + ) diff --git a/mayan/apps/sources/classes.py b/mayan/apps/sources/classes.py index 7448ae4a4e..ef9919e5ad 100644 --- a/mayan/apps/sources/classes.py +++ b/mayan/apps/sources/classes.py @@ -33,7 +33,9 @@ class SourceUploadedFile(File): class Attachment(File): def __init__(self, part, name): self.name = name - self.file = PseudoFile(StringIO(part.get_payload(decode=True)), name=name) + self.file = PseudoFile( + StringIO(part.get_payload(decode=True)), name=name + ) class StagingFile(object): @@ -45,7 +47,9 @@ class StagingFile(object): self.staging_folder = staging_folder if encoded_filename: self.encoded_filename = str(encoded_filename) - self.filename = base64.urlsafe_b64decode(urllib.unquote_plus(self.encoded_filename)) + self.filename = base64.urlsafe_b64decode( + urllib.unquote_plus(self.encoded_filename) + ) else: self.filename = filename self.encoded_filename = base64.urlsafe_b64encode(filename) @@ -54,7 +58,9 @@ class StagingFile(object): return unicode(self.filename) def as_file(self): - return File(file=open(self.get_full_path(), mode='rb'), name=self.filename) + return File( + file=open(self.get_full_path(), mode='rb'), name=self.filename + ) def get_full_path(self): return os.path.join(self.staging_folder.folder_path, self.filename) @@ -63,7 +69,11 @@ class StagingFile(object): converter = converter_class(file_object=open(self.get_full_path())) if size: - converter.transform(transformation=TransformationResize(**dict(zip(('width', 'height'), (size.split('x')))))) + converter.transform( + transformation=TransformationResize( + **dict(zip(('width', 'height'), (size.split('x')))) + ) + ) # Interactive transformations for transformation in transformations: diff --git a/mayan/apps/sources/forms.py b/mayan/apps/sources/forms.py index 6e13550f76..2869d78ac0 100644 --- a/mayan/apps/sources/forms.py +++ b/mayan/apps/sources/forms.py @@ -60,7 +60,6 @@ class StagingUploadForm(UploadBaseForm): ] except Exception as exception: logger.error('exception: %s', exception) - pass staging_file_id = forms.ChoiceField(label=_('Staging file')) diff --git a/mayan/apps/sources/handlers.py b/mayan/apps/sources/handlers.py index 1c71eefa6b..7fc23b8684 100644 --- a/mayan/apps/sources/handlers.py +++ b/mayan/apps/sources/handlers.py @@ -9,7 +9,9 @@ from .models import POP3Email, IMAPEmail, WatchFolderSource, WebFormSource def create_default_document_source(sender, **kwargs): - WebFormSource.objects.create(label=_('Default'), uncompress=SOURCE_UNCOMPRESS_CHOICE_ASK) + WebFormSource.objects.create( + label=_('Default'), uncompress=SOURCE_UNCOMPRESS_CHOICE_ASK + ) def copy_transformations_to_version(sender, **kwargs): @@ -17,7 +19,9 @@ def copy_transformations_to_version(sender, **kwargs): # TODO: Fix this, source should be previous version # TODO: Fix this, shouldn't this be at the documents app - Transformation.objects.copy(source=instance.document, targets=instance.pages.all()) + Transformation.objects.copy( + source=instance.document, targets=instance.pages.all() + ) def initialize_periodic_tasks(**kwargs): diff --git a/mayan/apps/sources/links.py b/mayan/apps/sources/links.py index 4ef1f2fba2..5851c8440f 100644 --- a/mayan/apps/sources/links.py +++ b/mayan/apps/sources/links.py @@ -17,17 +17,68 @@ from .permissions import ( ) -link_document_create_multiple = Link(icon='fa fa-upload', permissions=[permission_document_create], text=_('New document'), view='sources:document_create_multiple') -link_document_create_siblings = Link(permissions=[permission_document_create], text=_('Clone'), view='sources:document_create_siblings', args='object.id') -link_setup_sources = Link(icon='fa fa-upload', permissions=[permission_sources_setup_view], text=_('Sources'), view='sources:setup_source_list') -link_setup_source_create_imap_email = Link(permissions=[permission_sources_setup_create], text=_('Add new imap email'), view='sources:setup_source_create', args='"%s"' % SOURCE_CHOICE_EMAIL_IMAP) -link_setup_source_create_pop3_email = Link(permissions=[permission_sources_setup_create], text=_('Add new pop3 email'), view='sources:setup_source_create', args='"%s"' % SOURCE_CHOICE_EMAIL_POP3) -link_setup_source_create_staging_folder = Link(permissions=[permission_sources_setup_create], text=_('Add new staging folder'), view='sources:setup_source_create', args='"%s"' % SOURCE_CHOICE_STAGING) -link_setup_source_create_watch_folder = Link(permissions=[permission_sources_setup_create], text=_('Add new watch folder'), view='sources:setup_source_create', args='"%s"' % SOURCE_CHOICE_WATCH) -link_setup_source_create_webform = Link(permissions=[permission_sources_setup_create], text=_('Add new webform source'), view='sources:setup_source_create', args='"%s"' % SOURCE_CHOICE_WEB_FORM) -link_setup_source_delete = Link(permissions=[permission_sources_setup_delete], tags='dangerous', text=_('Delete'), view='sources:setup_source_delete', args=['resolved_object.pk']) -link_setup_source_edit = Link(text=_('Edit'), view='sources:setup_source_edit', args=['resolved_object.pk'], permissions=[permission_sources_setup_edit]) -link_source_list = Link(permissions=[permission_sources_setup_view], text=_('Document sources'), view='sources:setup_web_form_list') -link_staging_file_delete = Link(keep_query=True, permissions=[permission_document_new_version, permission_document_create], tags='dangerous', text=_('Delete'), view='sources:staging_file_delete', args=['source.pk', 'object.encoded_filename']) -link_upload_version = Link(permissions=[permission_document_new_version], text=_('Upload new version'), view='sources:upload_version', args='object.pk') -link_setup_source_logs = Link(text=_('Logs'), view='sources:setup_source_logs', args=['resolved_object.pk'], permissions=[permission_sources_setup_view]) +link_document_create_multiple = Link( + icon='fa fa-upload', permissions=[permission_document_create], + text=_('New document'), view='sources:document_create_multiple' +) +link_document_create_siblings = Link( + permissions=[permission_document_create], text=_('Clone'), + view='sources:document_create_siblings', args='object.id' +) +link_setup_sources = Link( + icon='fa fa-upload', permissions=[permission_sources_setup_view], + text=_('Sources'), view='sources:setup_source_list' +) +link_setup_source_create_imap_email = Link( + permissions=[permission_sources_setup_create], + text=_('Add new imap email'), view='sources:setup_source_create', + args='"%s"' % SOURCE_CHOICE_EMAIL_IMAP +) +link_setup_source_create_pop3_email = Link( + permissions=[permission_sources_setup_create], + text=_('Add new pop3 email'), view='sources:setup_source_create', + args='"%s"' % SOURCE_CHOICE_EMAIL_POP3 +) +link_setup_source_create_staging_folder = Link( + permissions=[permission_sources_setup_create], + text=_('Add new staging folder'), view='sources:setup_source_create', + args='"%s"' % SOURCE_CHOICE_STAGING +) +link_setup_source_create_watch_folder = Link( + permissions=[permission_sources_setup_create], + text=_('Add new watch folder'), view='sources:setup_source_create', + args='"%s"' % SOURCE_CHOICE_WATCH +) +link_setup_source_create_webform = Link( + permissions=[permission_sources_setup_create], + text=_('Add new webform source'), view='sources:setup_source_create', + args='"%s"' % SOURCE_CHOICE_WEB_FORM +) +link_setup_source_delete = Link( + permissions=[permission_sources_setup_delete], tags='dangerous', + text=_('Delete'), view='sources:setup_source_delete', + args=['resolved_object.pk'] +) +link_setup_source_edit = Link( + text=_('Edit'), view='sources:setup_source_edit', + args=['resolved_object.pk'], permissions=[permission_sources_setup_edit] +) +link_source_list = Link( + permissions=[permission_sources_setup_view], text=_('Document sources'), + view='sources:setup_web_form_list' +) +link_staging_file_delete = Link( + keep_query=True, + permissions=[permission_document_new_version, permission_document_create], + tags='dangerous', text=_('Delete'), view='sources:staging_file_delete', + args=['source.pk', 'object.encoded_filename'] +) +link_upload_version = Link( + permissions=[permission_document_new_version], + text=_('Upload new version'), view='sources:upload_version', + args='object.pk' +) +link_setup_source_logs = Link( + text=_('Logs'), view='sources:setup_source_logs', + args=['resolved_object.pk'], permissions=[permission_sources_setup_view] +) diff --git a/mayan/apps/sources/models.py b/mayan/apps/sources/models.py index 4109314fc6..d48c33d2cd 100644 --- a/mayan/apps/sources/models.py +++ b/mayan/apps/sources/models.py @@ -55,18 +55,31 @@ class Source(models.Model): def upload_document(self, file_object, document_type, description=None, label=None, language=None, metadata_dict_list=None, user=None): try: with transaction.atomic(): - document = Document.objects.create(description=description or '', document_type=document_type, label=label or unicode(file_object), language=language or setting_language.value) + document = Document.objects.create( + description=description or '', document_type=document_type, + label=label or unicode(file_object), + language=language or setting_language.value + ) document.save(_user=user) - document_version = document.new_version(file_object=file_object, _user=user) + document_version = document.new_version( + file_object=file_object, _user=user + ) - Transformation.objects.copy(source=self, targets=document_version.pages.all()) + Transformation.objects.copy( + source=self, targets=document_version.pages.all() + ) if metadata_dict_list: - save_metadata_list(metadata_dict_list, document, create=True) + save_metadata_list( + metadata_dict_list, document, create=True + ) except Exception as exception: - logger.critical('Unexpected exception while trying to create new document "%s" from source "%s"; %s', label or unicode(file_object), self, exception) + logger.critical( + 'Unexpected exception while trying to create new document "%s" from source "%s"; %s', + label or unicode(file_object), self, exception + ) raise def handle_upload(self, file_object, description=None, document_type=None, expand=False, label=None, language=None, metadata_dict_list=None, user=None): @@ -84,7 +97,9 @@ class Source(models.Model): compressed_file = CompressedFile(file_object) for compressed_file_child in compressed_file.children(): kwargs.update({'label': unicode(compressed_file_child)}) - self.upload_document(file_object=File(compressed_file_child), **kwargs) + self.upload_document( + file_object=File(compressed_file_child), **kwargs + ) compressed_file_child.close() except NotACompressedFile: @@ -119,11 +134,29 @@ class StagingFolderSource(InteractiveSource): is_interactive = True source_type = SOURCE_CHOICE_STAGING - folder_path = models.CharField(max_length=255, help_text=_('Server side filesystem path.'), verbose_name=_('Folder path')) - preview_width = models.IntegerField(help_text=_('Width value to be passed to the converter backend.'), verbose_name=_('Preview width')) - preview_height = models.IntegerField(blank=True, null=True, help_text=_('Height value to be passed to the converter backend.'), verbose_name=_('Preview height')) - uncompress = models.CharField(choices=SOURCE_INTERACTIVE_UNCOMPRESS_CHOICES, max_length=1, help_text=_('Whether to expand or not compressed archives.'), verbose_name=_('Uncompress')) - delete_after_upload = models.BooleanField(default=True, help_text=_('Delete the file after is has been successfully uploaded.'), verbose_name=_('Delete after upload')) + folder_path = models.CharField( + max_length=255, help_text=_('Server side filesystem path.'), + verbose_name=_('Folder path') + ) + preview_width = models.IntegerField( + help_text=_('Width value to be passed to the converter backend.'), + verbose_name=_('Preview width') + ) + preview_height = models.IntegerField( + blank=True, null=True, + help_text=_('Height value to be passed to the converter backend.'), + verbose_name=_('Preview height') + ) + uncompress = models.CharField( + choices=SOURCE_INTERACTIVE_UNCOMPRESS_CHOICES, max_length=1, + help_text=_('Whether to expand or not compressed archives.'), + verbose_name=_('Uncompress') + ) + delete_after_upload = models.BooleanField( + default=True, + help_text=_('Delete the file after is has been successfully uploaded.'), + verbose_name=_('Delete after upload') + ) def get_preview_size(self): dimensions = [] @@ -141,20 +174,34 @@ class StagingFolderSource(InteractiveSource): for entry in sorted([os.path.normcase(f) for f in os.listdir(self.folder_path) if os.path.isfile(os.path.join(self.folder_path, f))]): yield self.get_file(filename=entry) except OSError as exception: - logger.error('Unable get list of staging files from source: %s; %s', self, exception) - raise Exception(_('Unable get list of staging files: %s') % exception) + logger.error( + 'Unable get list of staging files from source: %s; %s', self, + exception + ) + raise Exception( + _('Unable get list of staging files: %s') % exception + ) def get_upload_file_object(self, form_data): - staging_file = self.get_file(encoded_filename=form_data['staging_file_id']) - return SourceUploadedFile(source=self, file=staging_file.as_file(), extra_data=staging_file) + staging_file = self.get_file( + encoded_filename=form_data['staging_file_id'] + ) + return SourceUploadedFile( + source=self, file=staging_file.as_file(), extra_data=staging_file + ) def clean_up_upload_file(self, upload_file_object): if self.delete_after_upload: try: upload_file_object.extra_data.delete() except Exception as exception: - logger.error('Error deleting staging file: %s; %s', upload_file_object, exception) - raise Exception(_('Error deleting staging file; %s') % exception) + logger.error( + 'Error deleting staging file: %s; %s', upload_file_object, + exception + ) + raise Exception( + _('Error deleting staging file; %s') % exception + ) class Meta: verbose_name = _('Staging folder') @@ -166,7 +213,11 @@ class WebFormSource(InteractiveSource): source_type = SOURCE_CHOICE_WEB_FORM # TODO: unify uncompress as an InteractiveSource field - uncompress = models.CharField(choices=SOURCE_INTERACTIVE_UNCOMPRESS_CHOICES, help_text=_('Whether to expand or not compressed archives.'), max_length=1, verbose_name=_('Uncompress')) + uncompress = models.CharField( + choices=SOURCE_INTERACTIVE_UNCOMPRESS_CHOICES, + help_text=_('Whether to expand or not compressed archives.'), + max_length=1, verbose_name=_('Uncompress') + ) # Default path def get_upload_file_object(self, form_data): @@ -186,16 +237,30 @@ class OutOfProcessSource(Source): class IntervalBaseModel(OutOfProcessSource): - interval = models.PositiveIntegerField(default=DEFAULT_INTERVAL, help_text=_('Interval in seconds between checks for new documents.'), verbose_name=_('Interval')) - document_type = models.ForeignKey(DocumentType, help_text=_('Assign a document type to documents uploaded from this source.'), verbose_name=_('Document type')) - uncompress = models.CharField(choices=SOURCE_UNCOMPRESS_CHOICES, help_text=_('Whether to expand or not, compressed archives.'), max_length=1, verbose_name=_('Uncompress')) + interval = models.PositiveIntegerField( + default=DEFAULT_INTERVAL, + help_text=_('Interval in seconds between checks for new documents.'), + verbose_name=_('Interval') + ) + document_type = models.ForeignKey( + DocumentType, + help_text=_('Assign a document type to documents uploaded from this source.'), + verbose_name=_('Document type') + ) + uncompress = models.CharField( + choices=SOURCE_UNCOMPRESS_CHOICES, + help_text=_('Whether to expand or not, compressed archives.'), + max_length=1, verbose_name=_('Uncompress') + ) def _get_periodic_task_name(self, pk=None): return 'check_interval_source-%i' % (pk or self.pk) def _delete_periodic_task(self, pk=None): try: - periodic_task = PeriodicTask.objects.get(name=self._get_periodic_task_name(pk)) + periodic_task = PeriodicTask.objects.get( + name=self._get_periodic_task_name(pk) + ) interval_instance = periodic_task.interval @@ -205,7 +270,10 @@ class IntervalBaseModel(OutOfProcessSource): else: periodic_task.delete() except PeriodicTask.DoesNotExist: - logger.warning('Tried to delete non existant periodic task "%s"', self._get_periodic_task_name(pk)) + logger.warning( + 'Tried to delete non existant periodic task "%s"', + self._get_periodic_task_name(pk) + ) def save(self, *args, **kwargs): new_source = not self.pk @@ -214,7 +282,9 @@ class IntervalBaseModel(OutOfProcessSource): if not new_source: self._delete_periodic_task() - interval_instance, created = IntervalSchedule.objects.get_or_create(every=self.interval, period='seconds') + interval_instance, created = IntervalSchedule.objects.get_or_create( + every=self.interval, period='seconds' + ) # Create a new interval or reuse someone else's PeriodicTask.objects.create( name=self._get_periodic_task_name(), @@ -236,7 +306,10 @@ class IntervalBaseModel(OutOfProcessSource): class EmailBaseModel(IntervalBaseModel): host = models.CharField(max_length=128, verbose_name=_('Host')) ssl = models.BooleanField(default=True, verbose_name=_('SSL')) - port = models.PositiveIntegerField(blank=True, null=True, help_text=_('Typical choices are 110 for POP3, 995 for POP3 over SSL, 143 for IMAP, 993 for IMAP over SSL.'), verbose_name=_('Port')) + port = models.PositiveIntegerField(blank=True, null=True, help_text=_( + 'Typical choices are 110 for POP3, 995 for POP3 over SSL, 143 for IMAP, 993 for IMAP over SSL.'), + verbose_name=_('Port') + ) username = models.CharField(max_length=96, verbose_name=_('Username')) password = models.CharField(max_length=96, verbose_name=_('Password')) @@ -264,7 +337,11 @@ class EmailBaseModel(IntervalBaseModel): logger.debug('filename: %s', filename) file_object = Attachment(part, name=filename) - source.handle_upload(document_type=source.document_type, file_object=file_object, label=filename, expand=(source.uncompress == SOURCE_UNCOMPRESS_CHOICE_Y)) + source.handle_upload( + document_type=source.document_type, + file_object=file_object, label=filename, + expand=(source.uncompress == SOURCE_UNCOMPRESS_CHOICE_Y) + ) class Meta: verbose_name = _('Email source') @@ -274,7 +351,9 @@ class EmailBaseModel(IntervalBaseModel): class POP3Email(EmailBaseModel): source_type = SOURCE_CHOICE_EMAIL_POP3 - timeout = models.PositiveIntegerField(default=DEFAULT_POP3_TIMEOUT, verbose_name=_('Timeout')) + timeout = models.PositiveIntegerField( + default=DEFAULT_POP3_TIMEOUT, verbose_name=_('Timeout') + ) def check_source(self): logger.debug('Starting POP3 email fetch') @@ -302,7 +381,9 @@ class POP3Email(EmailBaseModel): complete_message = '\n'.join(mailbox.retr(message_number)[1]) - EmailBaseModel.process_message(source=self, message=complete_message) + EmailBaseModel.process_message( + source=self, message=complete_message + ) mailbox.dele(message_number) mailbox.quit() @@ -315,7 +396,11 @@ class POP3Email(EmailBaseModel): class IMAPEmail(EmailBaseModel): source_type = SOURCE_CHOICE_EMAIL_IMAP - mailbox = models.CharField(default=DEFAULT_IMAP_MAILBOX, help_text=_('Mail from which to check for messages with attached documents.'), max_length=64, verbose_name=_('Mailbox')) + mailbox = models.CharField( + default=DEFAULT_IMAP_MAILBOX, + help_text=_('Mail from which to check for messages with attached documents.'), + max_length=64, verbose_name=_('Mailbox') + ) # http://www.doughellmann.com/PyMOTW/imaplib/ def check_source(self): @@ -354,7 +439,10 @@ class IMAPEmail(EmailBaseModel): class WatchFolderSource(IntervalBaseModel): source_type = SOURCE_CHOICE_WATCH - folder_path = models.CharField(help_text=_('Server side filesystem path.'), max_length=255, verbose_name=_('Folder path')) + folder_path = models.CharField( + help_text=_('Server side filesystem path.'), max_length=255, + verbose_name=_('Folder path') + ) def check_source(self): # Force self.folder_path to unicode to avoid os.listdir returning @@ -363,7 +451,11 @@ class WatchFolderSource(IntervalBaseModel): full_path = os.path.join(self.folder_path, file_name) if os.path.isfile(full_path): with File(file=open(full_path, mode='rb')) as file_object: - self.handle_upload(file_object=file_object, expand=(self.uncompress == SOURCE_UNCOMPRESS_CHOICE_Y), label=file_name) + self.handle_upload( + file_object=file_object, + expand=(self.uncompress == SOURCE_UNCOMPRESS_CHOICE_Y), + label=file_name + ) os.unlink(full_path) class Meta: @@ -372,9 +464,15 @@ class WatchFolderSource(IntervalBaseModel): class SourceLog(models.Model): - source = models.ForeignKey(Source, related_name='logs', verbose_name=_('Source')) - datetime = models.DateTimeField(auto_now_add=True, editable=False, verbose_name=_('Date time')) - message = models.TextField(blank=True, editable=False, verbose_name=_('Message')) + source = models.ForeignKey( + Source, related_name='logs', verbose_name=_('Source') + ) + datetime = models.DateTimeField( + auto_now_add=True, editable=False, verbose_name=_('Date time') + ) + message = models.TextField( + blank=True, editable=False, verbose_name=_('Message') + ) class Meta: verbose_name = _('Log entry') diff --git a/mayan/apps/sources/permissions.py b/mayan/apps/sources/permissions.py index 3be4d79177..9ca6d8dc79 100644 --- a/mayan/apps/sources/permissions.py +++ b/mayan/apps/sources/permissions.py @@ -5,7 +5,15 @@ from django.utils.translation import ugettext_lazy as _ from permissions import PermissionNamespace namespace = PermissionNamespace('sources_setup', _('Sources setup')) -permission_sources_setup_create = namespace.add_permission(name='sources_setup_create', label=_('Create new document sources')) -permission_sources_setup_delete = namespace.add_permission(name='sources_setup_delete', label=_('Delete document sources')) -permission_sources_setup_edit = namespace.add_permission(name='sources_setup_edit', label=_('Edit document sources')) -permission_sources_setup_view = namespace.add_permission(name='sources_setup_view', label=_('View existing document sources')) +permission_sources_setup_create = namespace.add_permission( + name='sources_setup_create', label=_('Create new document sources') +) +permission_sources_setup_delete = namespace.add_permission( + name='sources_setup_delete', label=_('Delete document sources') +) +permission_sources_setup_edit = namespace.add_permission( + name='sources_setup_edit', label=_('Edit document sources') +) +permission_sources_setup_view = namespace.add_permission( + name='sources_setup_view', label=_('View existing document sources') +) diff --git a/mayan/apps/sources/serializers.py b/mayan/apps/sources/serializers.py index d40a2ca350..0c4631d780 100644 --- a/mayan/apps/sources/serializers.py +++ b/mayan/apps/sources/serializers.py @@ -16,10 +16,18 @@ class StagingFolderFileSerializer(serializers.Serializer): filename = serializers.CharField(max_length=255) def get_url(self, obj): - return reverse('stagingfolderfile-detail', args=[obj.staging_folder.pk, obj.encoded_filename], request=self.context.get('request')) + return reverse( + 'stagingfolderfile-detail', + args=[obj.staging_folder.pk, obj.encoded_filename], + request=self.context.get('request') + ) def get_image_url(self, obj): - return reverse('stagingfolderfile-image-view', args=[obj.staging_folder.pk, obj.encoded_filename], request=self.context.get('request')) + return reverse( + 'stagingfolderfile-image-view', + args=[obj.staging_folder.pk, obj.encoded_filename], + request=self.context.get('request') + ) class StagingFolderSerializer(serializers.HyperlinkedModelSerializer): @@ -27,7 +35,9 @@ class StagingFolderSerializer(serializers.HyperlinkedModelSerializer): def get_files(self, obj): try: - return [StagingFolderFileSerializer(entry, context=self.context).data for entry in obj.get_files()] + return [ + StagingFolderFileSerializer(entry, context=self.context).data for entry in obj.get_files() + ] except Exception as exception: logger.error('unhandled exception: %s', exception) return [] diff --git a/mayan/apps/sources/tasks.py b/mayan/apps/sources/tasks.py index b1960021c5..10d4fe9ff5 100644 --- a/mayan/apps/sources/tasks.py +++ b/mayan/apps/sources/tasks.py @@ -25,7 +25,9 @@ def task_check_interval_source(source_id): source.check_source() except Exception as exception: logger.error('Error processing source: %s; %s', source, exception) - source.logs.create(message=_('Error processing source: %s') % exception) + source.logs.create( + message=_('Error processing source: %s') % exception + ) else: source.logs.all().delete() @@ -43,29 +45,44 @@ def task_upload_document(self, source_id, document_type_id, shared_uploaded_file user = None with shared_upload.open() as file_object: - source.upload_document(file_object=file_object, document_type=document_type, description=description, label=label, language=language, metadata_dict_list=metadata_dict_list, user=user) + source.upload_document( + file_object=file_object, document_type=document_type, + description=description, label=label, language=language, + metadata_dict_list=metadata_dict_list, user=user + ) except OperationalError as exception: - logger.warning('Operational exception while trying to create new document "%s" from source id %d; %s. Retying.', label or shared_upload.filename, source_id, exception) + logger.warning( + 'Operational exception while trying to create new document "%s" from source id %d; %s. Retying.', + label or shared_upload.filename, source_id, exception + ) raise self.retry(exc=exception) else: try: shared_upload.delete() except OperationalError as exception: - logger.warning('Operational error during attempt to delete shared upload file: %s; %s. Retrying.', shared_upload, exception) + logger.warning( + 'Operational error during attempt to delete shared upload file: %s; %s. Retrying.', + shared_upload, exception + ) @app.task(bind=True, default_retry_delay=DEFAULT_SOURCE_TASK_RETRY_DELAY, ignore_result=True) def task_source_handle_upload(self, document_type_id, shared_uploaded_file_id, source_id, description=None, expand=False, label=None, language=None, metadata_dict_list=None, skip_list=None, user_id=None): try: document_type = DocumentType.objects.get(pk=document_type_id) - shared_upload = SharedUploadedFile.objects.get(pk=shared_uploaded_file_id) + shared_upload = SharedUploadedFile.objects.get( + pk=shared_uploaded_file_id + ) if not label: label = shared_upload.filename except OperationalError as exception: - logger.warning('Operational error during attempt to load data to handle source upload: %s. Retrying.', exception) + logger.warning( + 'Operational error during attempt to load data to handle source upload: %s. Retrying.', + exception + ) raise self.retry(exc=exception) kwargs = { @@ -89,9 +106,14 @@ def task_source_handle_upload(self, document_type_id, shared_uploaded_file_id, s kwargs.update({'label': unicode(compressed_file_child)}) try: - child_shared_uploaded_file = SharedUploadedFile.objects.create(file=File(compressed_file_child)) + child_shared_uploaded_file = SharedUploadedFile.objects.create( + file=File(compressed_file_child) + ) except OperationalError as exception: - logger.warning('Operational error while preparing to upload child document: %s. Rescheduling.', exception) + logger.warning( + 'Operational error while preparing to upload child document: %s. Rescheduling.', + exception + ) task_source_handle_upload.delay( document_type_id=document_type_id, @@ -105,7 +127,10 @@ def task_source_handle_upload(self, document_type_id, shared_uploaded_file_id, s return else: skip_list.append(unicode(compressed_file_child)) - task_upload_document.delay(shared_uploaded_file_id=child_shared_uploaded_file.pk, **kwargs) + task_upload_document.delay( + shared_uploaded_file_id=child_shared_uploaded_file.pk, + **kwargs + ) finally: compressed_file_child.close() @@ -113,9 +138,16 @@ def task_source_handle_upload(self, document_type_id, shared_uploaded_file_id, s try: shared_upload.delete() except OperationalError as exception: - logger.warning('Operational error during attempt to delete shared upload file: %s; %s. Retrying.', shared_upload, exception) + logger.warning( + 'Operational error during attempt to delete shared upload file: %s; %s. Retrying.', + shared_upload, exception + ) except NotACompressedFile: logging.debug('Exception: NotACompressedFile') - task_upload_document.delay(shared_uploaded_file_id=shared_upload.pk, **kwargs) + task_upload_document.delay( + shared_uploaded_file_id=shared_upload.pk, **kwargs + ) else: - task_upload_document.delay(shared_uploaded_file_id=shared_upload.pk, **kwargs) + task_upload_document.delay( + shared_uploaded_file_id=shared_upload.pk, **kwargs + ) diff --git a/mayan/apps/sources/test_models.py b/mayan/apps/sources/test_models.py index 9067986075..bf8f73b087 100644 --- a/mayan/apps/sources/test_models.py +++ b/mayan/apps/sources/test_models.py @@ -27,12 +27,17 @@ class UploadDocumentTestCase(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() def tearDown(self): @@ -41,14 +46,17 @@ class UploadDocumentTestCase(TestCase): def test_issue_gh_163(self): """ - Non-ASCII chars in document name failing in upload via watch folder #163 - https://github.com/mayan-edms/mayan-edms/issues/163 + Non-ASCII chars in document name failing in upload via watch folder + gh-issue #163 https://github.com/mayan-edms/mayan-edms/issues/163 """ temporary_directory = tempfile.mkdtemp() shutil.copy(TEST_NON_ASCII_DOCUMENT_PATH, temporary_directory) - watch_folder = WatchFolderSource.objects.create(document_type=self.document_type, folder_path=temporary_directory, uncompress=SOURCE_UNCOMPRESS_CHOICE_Y) + watch_folder = WatchFolderSource.objects.create( + document_type=self.document_type, folder_path=temporary_directory, + uncompress=SOURCE_UNCOMPRESS_CHOICE_Y + ) watch_folder.check_source() self.assertEqual(Document.objects.count(), 1) @@ -65,7 +73,9 @@ class UploadDocumentTestCase(TestCase): # Test Non-ASCII named documents inside Non-ASCII named compressed file - shutil.copy(TEST_NON_ASCII_COMPRESSED_DOCUMENT_PATH, temporary_directory) + shutil.copy( + TEST_NON_ASCII_COMPRESSED_DOCUMENT_PATH, temporary_directory + ) watch_folder.check_source() document = Document.objects.all()[1] @@ -85,7 +95,9 @@ class UploadDocumentTestCase(TestCase): class CompressedUploadsTestCase(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 @@ -95,12 +107,25 @@ class CompressedUploadsTestCase(TestCase): self.document_type.delete() def test_upload_compressed_file(self): - source = WebFormSource(label='test source', uncompress=SOURCE_UNCOMPRESS_CHOICE_Y) + source = WebFormSource( + label='test source', uncompress=SOURCE_UNCOMPRESS_CHOICE_Y + ) with open(TEST_COMPRESSED_DOCUMENT_PATH) as file_object: - #self.document = self.document_type.new_document(file_object=File(file_object), label='small document') - source.handle_upload(document_type=self.document_type, file_object=File(file_object), expand=(source.uncompress == SOURCE_UNCOMPRESS_CHOICE_Y)) + source.handle_upload( + document_type=self.document_type, + file_object=File(file_object), + expand=(source.uncompress == SOURCE_UNCOMPRESS_CHOICE_Y) + ) self.assertEqual(Document.objects.count(), 2) - self.assertTrue('first document.pdf' in Document.objects.values_list('label', flat=True)) - self.assertTrue('second document.pdf' in Document.objects.values_list('label', flat=True)) + self.assertTrue( + 'first document.pdf' in Document.objects.values_list( + 'label', flat=True + ) + ) + self.assertTrue( + 'second document.pdf' in Document.objects.values_list( + 'label', flat=True + ) + ) diff --git a/mayan/apps/sources/test_views.py b/mayan/apps/sources/test_views.py index a2c3d67270..0c5825d3f4 100644 --- a/mayan/apps/sources/test_views.py +++ b/mayan/apps/sources/test_views.py @@ -22,12 +22,17 @@ class UploadDocumentTestCase(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() def tearDown(self): @@ -36,17 +41,30 @@ class UploadDocumentTestCase(TestCase): def test_upload_a_document(self): # 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()) # Create new webform source - self.client.post(reverse('sources:setup_source_create', args=[SOURCE_CHOICE_WEB_FORM]), {'label': 'test', 'uncompress': 'n', 'enabled': True}) + self.client.post( + reverse( + 'sources:setup_source_create', args=[SOURCE_CHOICE_WEB_FORM] + ), {'label': 'test', 'uncompress': 'n', 'enabled': True} + ) self.assertEqual(WebFormSource.objects.count(), 1) # Upload the test document with open(TEST_DOCUMENT_PATH) as file_descriptor: - self.client.post(reverse('sources:upload_interactive'), {'document-language': 'eng', 'source-file': file_descriptor, 'document_type_id': self.document_type.pk, 'label': 'mayan_11_1.pdf'}) + self.client.post( + reverse('sources:upload_interactive'), + { + 'document-language': 'eng', 'source-file': file_descriptor, + 'document_type_id': self.document_type.pk, + 'label': 'mayan_11_1.pdf' + } + ) self.assertEqual(Document.objects.count(), 1) self.document = Document.objects.all().first() @@ -56,22 +74,37 @@ class UploadDocumentTestCase(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_issue_25(self): # 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()) # Create new webform source - self.client.post(reverse('sources:setup_source_create', args=[SOURCE_CHOICE_WEB_FORM]), {'label': 'test', 'uncompress': 'n', 'enabled': True}) + self.client.post( + reverse( + 'sources:setup_source_create', args=[SOURCE_CHOICE_WEB_FORM] + ), {'label': 'test', 'uncompress': 'n', 'enabled': True} + ) self.assertEqual(WebFormSource.objects.count(), 1) # Upload the test document with open(TEST_SMALL_DOCUMENT_PATH) as file_descriptor: - self.client.post(reverse('sources:upload_interactive'), {'document-language': 'eng', 'source-file': file_descriptor, 'document_type_id': self.document_type.pk}) + self.client.post( + reverse('sources:upload_interactive'), + { + 'document-language': 'eng', 'source-file': file_descriptor, + 'document_type_id': self.document_type.pk + } + ) self.assertEqual(Document.objects.count(), 1) document = Document.objects.first() @@ -85,7 +118,13 @@ class UploadDocumentTestCase(TestCase): self.assertEqual(document.description, TEST_DOCUMENT_DESCRIPTION) # Test for issue 25 during editing - self.client.post(reverse('documents:document_edit', args=[document.pk]), {'description': TEST_DOCUMENT_DESCRIPTION, 'language': document.language, 'label': document.label}) + self.client.post( + reverse('documents:document_edit', args=[document.pk]), + { + 'description': TEST_DOCUMENT_DESCRIPTION, + 'language': document.language, 'label': document.label + } + ) # Fetch document again and test description document = Document.objects.first() self.assertEqual(document.description, TEST_DOCUMENT_DESCRIPTION) diff --git a/mayan/apps/sources/urls.py b/mayan/apps/sources/urls.py index 2aec066a8f..bf40ded233 100644 --- a/mayan/apps/sources/urls.py +++ b/mayan/apps/sources/urls.py @@ -15,32 +15,81 @@ from .wizards import DocumentCreateWizard urlpatterns = patterns( 'sources.views', - url(r'^staging_file/(?P\d+)/(?P.+)/delete/$', 'staging_file_delete', name='staging_file_delete'), + url( + r'^staging_file/(?P\d+)/(?P.+)/delete/$', + 'staging_file_delete', name='staging_file_delete' + ), - url(r'^upload/document/new/interactive/(?P\d+)/$', UploadInteractiveView.as_view(), name='upload_interactive'), - url(r'^upload/document/new/interactive/$', UploadInteractiveView.as_view(), name='upload_interactive'), + url( + r'^upload/document/new/interactive/(?P\d+)/$', + UploadInteractiveView.as_view(), name='upload_interactive' + ), + url( + r'^upload/document/new/interactive/$', UploadInteractiveView.as_view(), + name='upload_interactive' + ), - url(r'^upload/document/(?P\d+)/version/interactive/(?P\d+)/$', UploadInteractiveVersionView.as_view(), name='upload_version'), - url(r'^upload/document/(?P\d+)/version/interactive/$', UploadInteractiveVersionView.as_view(), name='upload_version'), + url( + r'^upload/document/(?P\d+)/version/interactive/(?P\d+)/$', + UploadInteractiveVersionView.as_view(), name='upload_version' + ), + url( + r'^upload/document/(?P\d+)/version/interactive/$', + UploadInteractiveVersionView.as_view(), name='upload_version' + ), # Setup views - url(r'^setup/list/$', SetupSourceListView.as_view(), name='setup_source_list'), - url(r'^setup/(?P\d+)/edit/$', SetupSourceEditView.as_view(), name='setup_source_edit'), - url(r'^setup/(?P\d+)/logs/$', SourceLogListView.as_view(), name='setup_source_logs'), - url(r'^setup/(?P\d+)/delete/$', SetupSourceDeleteView.as_view(), name='setup_source_delete'), - url(r'^setup/(?P\w+)/create/$', SetupSourceCreateView.as_view(), name='setup_source_create'), + url( + r'^setup/list/$', SetupSourceListView.as_view(), + name='setup_source_list' + ), + url( + r'^setup/(?P\d+)/edit/$', SetupSourceEditView.as_view(), + name='setup_source_edit' + ), + url( + r'^setup/(?P\d+)/logs/$', SourceLogListView.as_view(), + name='setup_source_logs' + ), + url( + r'^setup/(?P\d+)/delete/$', SetupSourceDeleteView.as_view(), + name='setup_source_delete' + ), + url( + r'^setup/(?P\w+)/create/$', + SetupSourceCreateView.as_view(), name='setup_source_create' + ), # Document create views - url(r'^create/from/local/multiple/$', DocumentCreateWizard.as_view(), name='document_create_multiple'), - url(r'^(?P\d+)/create/siblings/$', 'document_create_siblings', name='document_create_siblings'), + url( + r'^create/from/local/multiple/$', DocumentCreateWizard.as_view(), + name='document_create_multiple' + ), + url( + r'^(?P\d+)/create/siblings/$', 'document_create_siblings', + name='document_create_siblings' + ), ) api_urls = patterns( '', - url(r'^staging_folders/file/(?P[0-9]+)/(?P.+)/image/$', APIStagingSourceFileImageView.as_view(), name='stagingfolderfile-image-view'), - url(r'^staging_folders/file/(?P[0-9]+)/(?P.+)/$', APIStagingSourceFileView.as_view(), name='stagingfolderfile-detail'), - url(r'^staging_folders/$', APIStagingSourceListView.as_view(), name='stagingfolder-list'), - url(r'^staging_folders/(?P[0-9]+)/$', APIStagingSourceView.as_view(), name='stagingfolder-detail') + url( + r'^staging_folders/file/(?P[0-9]+)/(?P.+)/image/$', + APIStagingSourceFileImageView.as_view(), + name='stagingfolderfile-image-view' + ), + url( + r'^staging_folders/file/(?P[0-9]+)/(?P.+)/$', + APIStagingSourceFileView.as_view(), name='stagingfolderfile-detail' + ), + url( + r'^staging_folders/$', APIStagingSourceListView.as_view(), + name='stagingfolder-list' + ), + url( + r'^staging_folders/(?P[0-9]+)/$', APIStagingSourceView.as_view(), + name='stagingfolder-detail' + ) ) diff --git a/mayan/apps/sources/views.py b/mayan/apps/sources/views.py index 35fedbe490..f8034037b5 100644 --- a/mayan/apps/sources/views.py +++ b/mayan/apps/sources/views.py @@ -32,7 +32,8 @@ from .forms import ( NewDocumentForm, NewVersionForm ) from .literals import ( - SOURCE_CHOICE_STAGING, SOURCE_CHOICE_WEB_FORM, SOURCE_UNCOMPRESS_CHOICE_ASK, + SOURCE_CHOICE_STAGING, SOURCE_CHOICE_WEB_FORM, + SOURCE_UNCOMPRESS_CHOICE_ASK, SOURCE_UNCOMPRESS_CHOICE_Y ) from .models import ( @@ -133,12 +134,18 @@ class UploadBaseView(MultiFormView): def dispatch(self, request, *args, **kwargs): if 'source_id' in kwargs: - self.source = get_object_or_404(Source.objects.filter(enabled=True).select_subclasses(), pk=kwargs['source_id']) + self.source = get_object_or_404( + Source.objects.filter(enabled=True).select_subclasses(), + pk=kwargs['source_id'] + ) else: self.source = InteractiveSource.objects.filter(enabled=True).select_subclasses().first() if InteractiveSource.objects.filter(enabled=True).count() == 0: - messages.error(request, _('No interactive document sources have been defined or none have been enabled, create one before proceeding.')) + messages.error( + request, + _('No interactive document sources have been defined or none have been enabled, create one before proceeding.') + ) return HttpResponseRedirect(reverse('sources:setup_source_list')) return super(UploadBaseView, self).dispatch(request, *args, **kwargs) @@ -198,9 +205,16 @@ class UploadInteractiveView(UploadBaseView): def dispatch(self, request, *args, **kwargs): self.subtemplates_list = [] - Permission.check_permissions(request.user, [permission_document_create]) + Permission.check_permissions( + request.user, [permission_document_create] + ) - self.document_type = get_object_or_404(DocumentType, pk=self.request.GET.get('document_type_id', self.request.POST.get('document_type_id'))) + self.document_type = get_object_or_404( + DocumentType, + pk=self.request.GET.get( + 'document_type_id', self.request.POST.get('document_type_id') + ) + ) self.tab_links = get_active_tab_links() @@ -215,9 +229,13 @@ class UploadInteractiveView(UploadBaseView): else: expand = False - uploaded_file = self.source.get_upload_file_object(forms['source_form'].cleaned_data) + uploaded_file = self.source.get_upload_file_object( + forms['source_form'].cleaned_data + ) - shared_uploaded_file = SharedUploadedFile.objects.create(file=uploaded_file.file) + shared_uploaded_file = SharedUploadedFile.objects.create( + file=uploaded_file.file + ) if 'document_type_available_filenames' in forms['document_form'].cleaned_data: if forms['document_form'].cleaned_data['document_type_available_filenames']: @@ -246,14 +264,19 @@ class UploadInteractiveView(UploadBaseView): source_id=self.source.pk, user_id=user_id, )) - messages.success(self.request, _('New document queued for uploaded and will be available shortly.')) + messages.success( + self.request, + _('New document queued for uploaded and will be available shortly.') + ) return HttpResponseRedirect(self.request.get_full_path()) def create_source_form_form(self, **kwargs): return self.get_form_classes()['source_form']( prefix=kwargs['prefix'], source=self.source, - show_expand=(self.source.uncompress == SOURCE_UNCOMPRESS_CHOICE_ASK), + show_expand=( + self.source.uncompress == SOURCE_UNCOMPRESS_CHOICE_ASK + ), data=kwargs.get('data', None), files=kwargs.get('files', None), ) @@ -267,11 +290,16 @@ class UploadInteractiveView(UploadBaseView): ) def get_form_classes(self): - return {'document_form': NewDocumentForm, 'source_form': get_upload_form_class(self.source.source_type)} + return { + 'document_form': NewDocumentForm, + 'source_form': get_upload_form_class(self.source.source_type) + } def get_context_data(self, **kwargs): context = super(UploadInteractiveView, self).get_context_data(**kwargs) - context['title'] = _('Upload a local document from source: %s') % self.source.label + context['title'] = _( + 'Upload a local document from source: %s' + ) % self.source.label return context @@ -282,18 +310,27 @@ class UploadInteractiveVersionView(UploadBaseView): self.document = get_object_or_404(Document, pk=kwargs['document_pk']) try: - Permission.check_permissions(self.request.user, [permission_document_new_version]) + Permission.check_permissions( + self.request.user, [permission_document_new_version] + ) except PermissionDenied: - AccessControlList.objects.check_access(permission_document_new_version, self.request.user, self.document) + AccessControlList.objects.check_access( + permission_document_new_version, self.request.user, + self.document + ) self.tab_links = get_active_tab_links(self.document) return super(UploadInteractiveVersionView, self).dispatch(request, *args, **kwargs) def forms_valid(self, forms): - uploaded_file = self.source.get_upload_file_object(forms['source_form'].cleaned_data) + uploaded_file = self.source.get_upload_file_object( + forms['source_form'].cleaned_data + ) - shared_uploaded_file = SharedUploadedFile.objects.create(file=uploaded_file.file) + shared_uploaded_file = SharedUploadedFile.objects.create( + file=uploaded_file.file + ) try: self.source.clean_up_upload_file(uploaded_file) @@ -312,8 +349,13 @@ class UploadInteractiveVersionView(UploadBaseView): comment=forms['document_form'].cleaned_data.get('comment') )) - messages.success(self.request, _('New document version queued for uploaded and will be available shortly.')) - return HttpResponseRedirect(reverse('documents:document_version_list', args=[self.document.pk])) + messages.success( + self.request, + _('New document version queued for uploaded and will be available shortly.') + ) + return HttpResponseRedirect( + reverse('documents:document_version_list', args=[self.document.pk]) + ) def create_source_form_form(self, **kwargs): return self.get_form_classes()['source_form']( @@ -332,30 +374,47 @@ class UploadInteractiveVersionView(UploadBaseView): ) def get_form_classes(self): - return {'document_form': NewVersionForm, 'source_form': get_upload_form_class(self.source.source_type)} + return { + 'document_form': NewVersionForm, + 'source_form': get_upload_form_class(self.source.source_type) + } def get_context_data(self, **kwargs): context = super(UploadInteractiveVersionView, self).get_context_data(**kwargs) context['object'] = self.document - context['title'] = _('Upload a new version from source: %s') % self.source.label + context['title'] = _( + 'Upload a new version from source: %s' + ) % self.source.label return context def staging_file_delete(request, staging_folder_pk, encoded_filename): - Permission.check_permissions(request.user, [permission_document_create, permission_document_new_version]) - staging_folder = get_object_or_404(StagingFolderSource, pk=staging_folder_pk) + Permission.check_permissions( + request.user, [ + permission_document_create, permission_document_new_version + ] + ) + staging_folder = get_object_or_404( + StagingFolderSource, pk=staging_folder_pk + ) staging_file = staging_folder.get_file(encoded_filename=encoded_filename) - 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: staging_file.delete() messages.success(request, _('Staging file delete successfully.')) except Exception as exception: - messages.error(request, _('Staging file delete error; %s.') % exception) + messages.error( + request, _('Staging file delete error; %s.') % exception + ) return HttpResponseRedirect(next) results = get_active_tab_links() @@ -366,7 +425,11 @@ def staging_file_delete(request, staging_folder_pk, encoded_filename): 'object': staging_file, 'next': next, 'previous': previous, - 'extra_navigation_links': {'form_header': {'staging_file_delete': {'links': results['tab_links']}}}, + 'extra_navigation_links': { + 'form_header': { + 'staging_file_delete': {'links': results['tab_links']} + } + }, }, context_instance=RequestContext(request)) @@ -390,7 +453,9 @@ class SetupSourceDeleteView(SingleObjectDeleteView): view_permission = permission_sources_setup_delete def get_object(self): - return get_object_or_404(Source.objects.select_subclasses(), pk=self.kwargs['pk']) + return get_object_or_404( + Source.objects.select_subclasses(), pk=self.kwargs['pk'] + ) def get_form_class(self): return get_form_class(self.get_object().source_type) @@ -407,7 +472,9 @@ class SetupSourceEditView(SingleObjectEditView): view_permission = permission_sources_setup_edit def get_object(self): - return get_object_or_404(Source.objects.select_subclasses(), pk=self.kwargs['pk']) + return get_object_or_404( + Source.objects.select_subclasses(), pk=self.kwargs['pk'] + ) def get_form_class(self): return get_form_class(self.get_object().source_type) @@ -431,7 +498,9 @@ class SetupSourceListView(SingleObjectListView): }, { 'name': _('Enabled'), - 'attribute': encapsulate(lambda entry: two_state_template(entry.enabled)) + 'attribute': encapsulate( + lambda entry: two_state_template(entry.enabled) + ) }, ], 'hide_link': True, diff --git a/mayan/apps/sources/widgets.py b/mayan/apps/sources/widgets.py index f83d93ec56..437c673d13 100644 --- a/mayan/apps/sources/widgets.py +++ b/mayan/apps/sources/widgets.py @@ -14,7 +14,9 @@ from documents.settings import setting_preview_size, setting_thumbnail_size def staging_file_thumbnail(staging_file, **kwargs): - return staging_file_html_widget(staging_file, click_view='stagingfolderfile-image-view', **kwargs) + return staging_file_html_widget( + staging_file, click_view='stagingfolderfile-image-view', **kwargs + ) def staging_file_html_widget(staging_file, click_view=None, page=DEFAULT_PAGE_NUMBER, zoom=DEFAULT_ZOOM_LEVEL, rotation=DEFAULT_ROTATION, gallery_name=None, fancybox_class='fancybox-staging', image_class='lazy-load', title=None, size=setting_thumbnail_size.value, nolazyload=False): @@ -36,12 +38,25 @@ def staging_file_html_widget(staging_file, click_view=None, page=DEFAULT_PAGE_NU query_string = urlencode(query_dict) - preview_view = '%s?%s' % (reverse('stagingfolderfile-image-view', args=[staging_file.staging_folder.pk, staging_file.encoded_filename]), query_string) + preview_view = '%s?%s' % ( + reverse( + 'stagingfolderfile-image-view', + args=[ + staging_file.staging_folder.pk, staging_file.encoded_filename + ] + ), query_string + ) plain_template = [] - plain_template.append('%s' % (preview_view, alt_text)) + plain_template.append( + '%s' % (preview_view, alt_text) + ) - result.append('
    ' % (staging_file.filename, page if page else DEFAULT_PAGE_NUMBER)) + result.append( + '
    ' % ( + staging_file.filename, page if page else DEFAULT_PAGE_NUMBER + ) + ) if title: title_template = 'title="%s"' % strip_tags(title) @@ -52,12 +67,35 @@ def staging_file_html_widget(staging_file, click_view=None, page=DEFAULT_PAGE_NU # TODO: fix this hack query_dict['size'] = setting_preview_size.value query_string = urlencode(query_dict) - result.append('' % (gallery_template, fancybox_class, '%s?%s' % (reverse(click_view, args=[staging_file.staging_folder.pk, staging_file.encoded_filename]), query_string), title_template)) + result.append( + '' % ( + gallery_template, fancybox_class, + '%s?%s' % ( + reverse( + click_view, + args=[ + staging_file.staging_folder.pk, + staging_file.encoded_filename + ] + ), + query_string + ), title_template + ) + ) if nolazyload: - result.append('%s' % (preview_view, alt_text)) + result.append( + '%s' % ( + preview_view, alt_text + ) + ) else: - result.append('%s' % (image_class, preview_view, static('appearance/images/loading.png'), alt_text)) + result.append( + '%s' % ( + image_class, preview_view, + static('appearance/images/loading.png'), alt_text + ) + ) if click_view: result.append('') diff --git a/mayan/apps/sources/wizards.py b/mayan/apps/sources/wizards.py index bfdb258279..6f1a2e257e 100644 --- a/mayan/apps/sources/wizards.py +++ b/mayan/apps/sources/wizards.py @@ -31,7 +31,10 @@ class DocumentCreateWizard(ViewPermissionCheckMixin, SessionWizardView): def dispatch(self, request, *args, **kwargs): if InteractiveSource.objects.filter(enabled=True).count() == 0: - messages.error(request, _('No interactive document sources have been defined or none have been enabled, create one before proceeding.')) + messages.error( + request, + _('No interactive document sources have been defined or none have been enabled, create one before proceeding.') + ) return HttpResponseRedirect(reverse('sources:setup_source_list')) return super(DocumentCreateWizard, self).dispatch(request, *args, **kwargs) diff --git a/mayan/apps/statistics/apps.py b/mayan/apps/statistics/apps.py index 9970aeacae..ad74a105f7 100644 --- a/mayan/apps/statistics/apps.py +++ b/mayan/apps/statistics/apps.py @@ -19,6 +19,11 @@ class StatisticsApp(MayanAppConfig): super(StatisticsApp, self).ready() menu_object.bind_links(links=[link_execute], sources=[Statistic]) - menu_object.bind_links(links=[link_namespace_details], sources=[StatisticNamespace]) - menu_secondary.bind_links(links=[link_namespace_list], sources=[StatisticNamespace, 'statistics:namespace_list']) + menu_object.bind_links( + links=[link_namespace_details], sources=[StatisticNamespace] + ) + menu_secondary.bind_links( + links=[link_namespace_list], + sources=[StatisticNamespace, 'statistics:namespace_list'] + ) menu_tools.bind_links(links=[link_statistics]) diff --git a/mayan/apps/statistics/links.py b/mayan/apps/statistics/links.py index 530eefc351..f91b09c79d 100644 --- a/mayan/apps/statistics/links.py +++ b/mayan/apps/statistics/links.py @@ -7,7 +7,19 @@ from navigation import Link from .permissions import permission_statistics_view -link_execute = Link(permissions=[permission_statistics_view], text=_('Execute'), view='statistics:statistic_execute', args='resolved_object.id') -link_namespace_details = Link(permissions=[permission_statistics_view], text=_('Namespace details'), view='statistics:namespace_details', args='resolved_object.id') -link_namespace_list = Link(permissions=[permission_statistics_view], text=_('Namespace list'), view='statistics:namespace_list') -link_statistics = Link(icon='fa fa-sort-numeric-desc', permissions=[permission_statistics_view], text=_('Statistics'), view='statistics:namespace_list') +link_execute = Link( + permissions=[permission_statistics_view], text=_('Execute'), + view='statistics:statistic_execute', args='resolved_object.id' +) +link_namespace_details = Link( + permissions=[permission_statistics_view], text=_('Namespace details'), + view='statistics:namespace_details', args='resolved_object.id' +) +link_namespace_list = Link( + permissions=[permission_statistics_view], text=_('Namespace list'), + view='statistics:namespace_list' +) +link_statistics = Link( + icon='fa fa-sort-numeric-desc', permissions=[permission_statistics_view], + text=_('Statistics'), view='statistics:namespace_list' +) diff --git a/mayan/apps/statistics/permissions.py b/mayan/apps/statistics/permissions.py index c9c816f12b..a87d3ba6f3 100644 --- a/mayan/apps/statistics/permissions.py +++ b/mayan/apps/statistics/permissions.py @@ -6,4 +6,6 @@ from permissions import PermissionNamespace namespace = PermissionNamespace('statistics', _('Statistics')) -permission_statistics_view = namespace.add_permission(name='statistics_view', label=_('View statistics')) +permission_statistics_view = namespace.add_permission( + name='statistics_view', label=_('View statistics') +) diff --git a/mayan/apps/statistics/urls.py b/mayan/apps/statistics/urls.py index 20090a799a..a952799240 100644 --- a/mayan/apps/statistics/urls.py +++ b/mayan/apps/statistics/urls.py @@ -7,6 +7,12 @@ from .views import NamespaceDetailView, NamespaceListView, StatisticExecute urlpatterns = patterns( 'statistics.views', url(r'^$', NamespaceListView.as_view(), name='namespace_list'), - url(r'^namespace/(?P\w+)/details/$', NamespaceDetailView.as_view(), name='namespace_details'), - url(r'^(?P\w+)/execute/$', StatisticExecute.as_view(), name='statistic_execute'), + url( + r'^namespace/(?P\w+)/details/$', + NamespaceDetailView.as_view(), name='namespace_details' + ), + url( + r'^(?P\w+)/execute/$', StatisticExecute.as_view(), + name='statistic_execute' + ), ) diff --git a/mayan/apps/storage/settings.py b/mayan/apps/storage/settings.py index 077e84b23e..12184f50bd 100644 --- a/mayan/apps/storage/settings.py +++ b/mayan/apps/storage/settings.py @@ -8,4 +8,7 @@ from django.utils.translation import ugettext_lazy as _ from smart_settings import Namespace namespace = Namespace(name='storage', label=_('Storage')) -setting_filestorage_location = namespace.add_setting(global_name='STORAGE_FILESTORAGE_LOCATION', default=os.path.join(settings.MEDIA_ROOT, 'document_storage'), is_path=True) +setting_filestorage_location = namespace.add_setting( + global_name='STORAGE_FILESTORAGE_LOCATION', + default=os.path.join(settings.MEDIA_ROOT, 'document_storage'), is_path=True +) diff --git a/mayan/apps/storage/views.py b/mayan/apps/storage/views.py deleted file mode 100644 index 60f00ef0ef..0000000000 --- a/mayan/apps/storage/views.py +++ /dev/null @@ -1 +0,0 @@ -# Create your views here. diff --git a/mayan/apps/tags/api_views.py b/mayan/apps/tags/api_views.py index 243ee84418..fd5ffd2fd0 100644 --- a/mayan/apps/tags/api_views.py +++ b/mayan/apps/tags/api_views.py @@ -75,9 +75,13 @@ class APITagDocumentListView(generics.ListAPIView): def get_queryset(self): tag = get_object_or_404(Tag, pk=self.kwargs['pk']) try: - Permission.check_permissions(self.request.user, [permission_tag_view]) + Permission.check_permissions( + self.request.user, [permission_tag_view] + ) except PermissionDenied: - AccessControlList.objects.check_access(permission_tag_view, self.request.user, tag) + AccessControlList.objects.check_access( + permission_tag_view, self.request.user, tag + ) queryset = tag.documents.all() return queryset @@ -96,9 +100,13 @@ class APIDocumentTagListView(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.tags.all() return queryset @@ -114,7 +122,9 @@ class APIDocumentTagView(views.APIView): try: Permission.check_permissions(request.user, [permission_tag_remove]) except PermissionDenied: - AccessControlList.objects.check_access(permission_tag_remove, request.user, document) + AccessControlList.objects.check_access( + permission_tag_remove, request.user, document + ) tag = get_object_or_404(Tag, pk=self.kwargs['pk']) tag.documents.remove(document) @@ -129,7 +139,9 @@ class APIDocumentTagView(views.APIView): try: Permission.check_permissions(request.user, [permission_tag_attach]) except PermissionDenied: - AccessControlList.objects.check_access(permission_tag_attach, request.user, document) + AccessControlList.objects.check_access( + permission_tag_attach, request.user, document + ) tag = get_object_or_404(Tag, pk=self.kwargs['pk']) tag.documents.add(document) diff --git a/mayan/apps/tags/apps.py b/mayan/apps/tags/apps.py index 2a2f6dc261..30ec4d74ee 100644 --- a/mayan/apps/tags/apps.py +++ b/mayan/apps/tags/apps.py @@ -40,28 +40,66 @@ class TagsApp(MayanAppConfig): ModelPermission.register( model=Document, permissions=( - permission_tag_attach, permission_tag_remove, permission_tag_view + permission_tag_attach, permission_tag_remove, + permission_tag_view ) ) ModelPermission.register( model=Tag, permissions=( - permission_acl_edit, permission_acl_view, permission_tag_delete, - permission_tag_edit, permission_tag_view, + permission_acl_edit, permission_acl_view, + permission_tag_delete, permission_tag_edit, + permission_tag_view, ) ) - SourceColumn(source=Document, label=_('Tags'), attribute=encapsulate(lambda document: widget_inline_tags(document))) + SourceColumn( + source=Document, label=_('Tags'), + attribute=encapsulate( + lambda document: widget_inline_tags(document) + ) + ) - SourceColumn(source=Tag, label=_('Preview'), attribute=encapsulate(lambda tag: widget_single_tag(tag))) + SourceColumn( + source=Tag, label=_('Preview'), + attribute=encapsulate(lambda tag: widget_single_tag(tag)) + ) document_search.add_model_field(field='tags__label', label=_('Tags')) - menu_facet.bind_links(links=[link_tag_document_list], sources=[Document]) + menu_facet.bind_links( + links=[link_tag_document_list], sources=[Document] + ) menu_main.bind_links(links=[link_tag_list]) - menu_multi_item.bind_links(links=[link_multiple_documents_attach_tag, link_multiple_documents_tag_remove], sources=[Document]) - menu_multi_item.bind_links(links=[link_tag_multiple_delete], sources=[Tag]) - menu_multi_item.bind_links(links=[link_single_document_multiple_tag_remove], sources=[CombinedSource(obj=Tag, view='tags:document_tags')]) - menu_object.bind_links(links=[link_tag_tagged_item_list, link_tag_edit, link_acl_list, link_tag_delete], sources=[Tag]) - menu_secondary.bind_links(links=[link_tag_list, link_tag_create], sources=[Tag, 'tags:tag_list', 'tags:tag_create']) - menu_sidebar.bind_links(links=[link_tag_attach], sources=['tags:document_tags', 'tags:tag_remove', 'tags:tag_multiple_remove', 'tags:tag_attach']) + menu_multi_item.bind_links( + links=[ + link_multiple_documents_attach_tag, + link_multiple_documents_tag_remove + ], + sources=[Document] + ) + menu_multi_item.bind_links( + links=[link_tag_multiple_delete], sources=[Tag] + ) + menu_multi_item.bind_links( + links=[link_single_document_multiple_tag_remove], + sources=[CombinedSource(obj=Tag, view='tags:document_tags')] + ) + menu_object.bind_links( + links=[ + link_tag_tagged_item_list, link_tag_edit, link_acl_list, + link_tag_delete + ], + sources=[Tag] + ) + menu_secondary.bind_links( + links=[link_tag_list, link_tag_create], + sources=[Tag, 'tags:tag_list', 'tags:tag_create'] + ) + menu_sidebar.bind_links( + links=[link_tag_attach], + sources=[ + 'tags:document_tags', 'tags:tag_remove', + 'tags:tag_multiple_remove', 'tags:tag_attach' + ] + ) diff --git a/mayan/apps/tags/forms.py b/mayan/apps/tags/forms.py index 6ad19e0de6..748e54ba7a 100644 --- a/mayan/apps/tags/forms.py +++ b/mayan/apps/tags/forms.py @@ -25,7 +25,9 @@ class TagListForm(forms.Form): try: Permission.check_permissions(user, [permission_tag_view]) except PermissionDenied: - queryset = AccessControlList.objects.filter_by_access(permission_tag_view, user, queryset) + queryset = AccessControlList.objects.filter_by_access( + permission_tag_view, user, queryset + ) self.fields['tag'] = forms.ModelChoiceField( queryset=queryset, diff --git a/mayan/apps/tags/links.py b/mayan/apps/tags/links.py index 3dd6e21ff6..1b20b2175c 100644 --- a/mayan/apps/tags/links.py +++ b/mayan/apps/tags/links.py @@ -10,14 +10,41 @@ from .permissions import ( ) -link_multiple_documents_tag_remove = Link(text=_('Remove tag'), view='tags:multiple_documents_selection_tag_remove') -link_multiple_documents_attach_tag = Link(text=_('Attach tag'), view='tags:multiple_documents_tag_attach') -link_single_document_multiple_tag_remove = Link(permissions=[permission_tag_remove], text=_('remove tags'), view='tags:single_document_multiple_tag_remove', args='document.id') -link_tag_attach = Link(permissions=[permission_tag_attach], text=_('Attach tag'), view='tags:tag_attach', args='object.pk') -link_tag_create = Link(permissions=[permission_tag_create], text=_('Create new tag'), view='tags:tag_create') -link_tag_delete = Link(permissions=[permission_tag_delete], tags='dangerous', text=_('Delete'), view='tags:tag_delete', args='object.id') -link_tag_edit = Link(permissions=[permission_tag_edit], text=_('Edit'), view='tags:tag_edit', args='object.id') -link_tag_document_list = Link(permissions=[permission_tag_view], text=_('Tags'), view='tags:document_tags', args='object.pk') +link_multiple_documents_tag_remove = Link( + text=_('Remove tag'), view='tags:multiple_documents_selection_tag_remove' +) +link_multiple_documents_attach_tag = Link( + text=_('Attach tag'), view='tags:multiple_documents_tag_attach' +) +link_single_document_multiple_tag_remove = Link( + permissions=[permission_tag_remove], text=_('remove tags'), + view='tags:single_document_multiple_tag_remove', args='document.id' +) +link_tag_attach = Link( + permissions=[permission_tag_attach], text=_('Attach tag'), + view='tags:tag_attach', args='object.pk' +) +link_tag_create = Link( + permissions=[permission_tag_create], text=_('Create new tag'), + view='tags:tag_create' +) +link_tag_delete = Link( + permissions=[permission_tag_delete], tags='dangerous', text=_('Delete'), + view='tags:tag_delete', args='object.id' +) +link_tag_edit = Link( + permissions=[permission_tag_edit], text=_('Edit'), view='tags:tag_edit', + args='object.id' +) +link_tag_document_list = Link( + permissions=[permission_tag_view], text=_('Tags'), + view='tags:document_tags', args='object.pk' +) link_tag_list = Link(icon='fa fa-tag', text=_('Tags'), view='tags:tag_list') -link_tag_multiple_delete = Link(permissions=[permission_tag_delete], text=_('Delete'), view='tags:tag_multiple_delete') -link_tag_tagged_item_list = Link(text=('Documents'), view='tags:tag_tagged_item_list', args='object.id') +link_tag_multiple_delete = Link( + permissions=[permission_tag_delete], text=_('Delete'), + view='tags:tag_multiple_delete' +) +link_tag_tagged_item_list = Link( + text=('Documents'), view='tags:tag_tagged_item_list', args='object.id' +) diff --git a/mayan/apps/tags/models.py b/mayan/apps/tags/models.py index 767ccd528f..33bfc5996b 100644 --- a/mayan/apps/tags/models.py +++ b/mayan/apps/tags/models.py @@ -11,9 +11,13 @@ from documents.models import Document @python_2_unicode_compatible class Tag(models.Model): - label = models.CharField(db_index=True, max_length=128, unique=True, verbose_name=_('Label')) + label = models.CharField( + db_index=True, max_length=128, unique=True, verbose_name=_('Label') + ) color = RGBColorField(verbose_name=_('Color')) - documents = models.ManyToManyField(Document, related_name='tags', verbose_name=_('Documents')) + documents = models.ManyToManyField( + Document, related_name='tags', verbose_name=_('Documents') + ) class Meta: verbose_name = _('Tag') diff --git a/mayan/apps/tags/permissions.py b/mayan/apps/tags/permissions.py index 97b2522060..ba9d0f4146 100644 --- a/mayan/apps/tags/permissions.py +++ b/mayan/apps/tags/permissions.py @@ -6,9 +6,21 @@ from permissions import PermissionNamespace namespace = PermissionNamespace('tags', _('Tags')) -permission_tag_create = namespace.add_permission(name='tag_create', label=_('Create new tags')) -permission_tag_delete = namespace.add_permission(name='tag_delete', label=_('Delete tags')) -permission_tag_edit = namespace.add_permission(name='tag_edit', label=_('Edit tags')) -permission_tag_view = namespace.add_permission(name='tag_view', label=_('View tags')) -permission_tag_attach = namespace.add_permission(name='tag_attach', label=_('Attach tags to documents')) -permission_tag_remove = namespace.add_permission(name='tag_remove', label=_('Remove tags from documents')) +permission_tag_create = namespace.add_permission( + name='tag_create', label=_('Create new tags') +) +permission_tag_delete = namespace.add_permission( + name='tag_delete', label=_('Delete tags') +) +permission_tag_view = namespace.add_permission( + name='tag_view', label=_('View tags') +) +permission_tag_edit = namespace.add_permission( + name='tag_edit', label=_('Edit tags') +) +permission_tag_attach = namespace.add_permission( + name='tag_attach', label=_('Attach tags to documents') +) +permission_tag_remove = namespace.add_permission( + name='tag_remove', label=_('Remove tags from documents') +) diff --git a/mayan/apps/tags/test_models.py b/mayan/apps/tags/test_models.py index 6160c7eccf..59fd510b95 100644 --- a/mayan/apps/tags/test_models.py +++ b/mayan/apps/tags/test_models.py @@ -13,14 +13,18 @@ TAG_COLOR = '#FF0000' class TagTestCase(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)) + self.document = self.document_type.new_document( + file_object=File(file_object) + ) def tearDown(self): self.document.delete() diff --git a/mayan/apps/tags/urls.py b/mayan/apps/tags/urls.py index a276b457e9..84064b9732 100644 --- a/mayan/apps/tags/urls.py +++ b/mayan/apps/tags/urls.py @@ -17,16 +17,39 @@ urlpatterns = patterns( url(r'^create/$', TagCreateView.as_view(), name='tag_create'), url(r'^(?P\d+)/delete/$', 'tag_delete', name='tag_delete'), url(r'^(?P\d+)/edit/$', TagEditView.as_view(), name='tag_edit'), - url(r'^(?P\d+)/documents/$', TagTaggedItemListView.as_view(), name='tag_tagged_item_list'), - url(r'^multiple/delete/$', 'tag_multiple_delete', name='tag_multiple_delete'), + url( + r'^(?P\d+)/documents/$', TagTaggedItemListView.as_view(), + name='tag_tagged_item_list' + ), + url( + r'^multiple/delete/$', 'tag_multiple_delete', + name='tag_multiple_delete' + ), - url(r'^multiple/remove/document/(?P\d+)/$', 'single_document_multiple_tag_remove', name='single_document_multiple_tag_remove'), - url(r'^multiple/remove/document/multiple/$', 'multiple_documents_selection_tag_remove', name='multiple_documents_selection_tag_remove'), + url( + r'^multiple/remove/document/(?P\d+)/$', + 'single_document_multiple_tag_remove', + name='single_document_multiple_tag_remove' + ), + url( + r'^multiple/remove/document/multiple/$', + 'multiple_documents_selection_tag_remove', + name='multiple_documents_selection_tag_remove' + ), - url(r'^selection/attach/document/(?P\d+)/$', 'tag_attach', name='tag_attach'), - url(r'^selection/attach/document/multiple/$', 'tag_multiple_attach', name='multiple_documents_tag_attach'), + url( + r'^selection/attach/document/(?P\d+)/$', 'tag_attach', + name='tag_attach' + ), + url( + r'^selection/attach/document/multiple/$', 'tag_multiple_attach', + name='multiple_documents_tag_attach' + ), - url(r'^document/(?P\d+)/tags/$', DocumentTagListView.as_view(), name='document_tags'), + url( + r'^document/(?P\d+)/tags/$', DocumentTagListView.as_view(), + name='document_tags' + ), ) api_urls = patterns( diff --git a/mayan/apps/tags/views.py b/mayan/apps/tags/views.py index 683b2b6257..48176bf002 100644 --- a/mayan/apps/tags/views.py +++ b/mayan/apps/tags/views.py @@ -44,19 +44,31 @@ def tag_attach(request, document_id=None, document_id_list=None): documents = [get_object_or_404(Document, pk=document_id)] post_action_redirect = reverse('tags:tag_list') elif document_id_list: - documents = [get_object_or_404(Document, pk=document_id) for document_id in document_id_list.split(',')] + documents = [ + get_object_or_404(Document, pk=document_id) for document_id in document_id_list.split(',') + ] else: 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_tag_attach]) except PermissionDenied: - documents = AccessControlList.objects.filter_by_access(permission_tag_attach, request.user, documents) + documents = AccessControlList.objects.filter_by_access( + permission_tag_attach, request.user, documents + ) post_action_redirect = None - 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)))) + 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))) + ) if request.method == 'POST': form = TagListForm(request.POST, user=request.user) @@ -64,13 +76,22 @@ def tag_attach(request, document_id=None, document_id_list=None): tag = form.cleaned_data['tag'] for document in documents: if tag in document.tags.all(): - messages.warning(request, _('Document "%(document)s" is already tagged as "%(tag)s"') % { - 'document': document, 'tag': tag} + messages.warning( + request, _( + 'Document "%(document)s" is already tagged as "%(tag)s"' + ) % { + 'document': document, 'tag': tag + } ) else: tag.documents.add(document) - messages.success(request, _('Tag "%(tag)s" attached successfully to document "%(document)s".') % { - 'document': document, 'tag': tag} + messages.success( + request, + _( + 'Tag "%(tag)s" attached successfully to document "%(document)s".' + ) % { + 'document': document, 'tag': tag + } ) return HttpResponseRedirect(next) else: @@ -90,8 +111,10 @@ def tag_attach(request, document_id=None, document_id_list=None): if len(documents) == 1: context['object'] = documents[0] - return render_to_response('appearance/generic_form.html', context, - context_instance=RequestContext(request)) + return render_to_response( + 'appearance/generic_form.html', context, + context_instance=RequestContext(request) + ) def tag_multiple_attach(request): @@ -108,7 +131,9 @@ class TagListView(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() @@ -124,7 +149,14 @@ class TagListView(SingleObjectListView): def get_extra_context(self, **kwargs): return { 'extra_columns': [ - {'name': _('Documents'), 'attribute': encapsulate(lambda instance: TagListView.get_document_count(instance=instance, user=self.request.user))}, + { + 'name': _('Documents'), + 'attribute': encapsulate( + lambda instance: TagListView.get_document_count( + instance=instance, user=self.request.user + ) + ) + }, ], 'hide_link': True, @@ -142,12 +174,18 @@ def tag_delete(request, tag_id=None, tag_id_list=None): tags = [get_object_or_404(Tag, pk=tag_id) for tag_id in tag_id_list.split(',')] else: messages.error(request, _('Must provide at least one tag.')) - 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_tag_delete]) except PermissionDenied: - tags = AccessControlList.objects.filter_by_access(permission_tag_delete, request.user, tags) + tags = AccessControlList.objects.filter_by_access( + permission_tag_delete, request.user, tags + ) 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)))) @@ -156,11 +194,15 @@ def tag_delete(request, tag_id=None, tag_id_list=None): for tag in tags: try: tag.delete() - messages.success(request, _('Tag "%s" deleted successfully.') % tag) + messages.success( + request, _('Tag "%s" deleted successfully.') % tag + ) except Exception as exception: - messages.error(request, _('Error deleting tag "%(tag)s": %(error)s') % { - 'tag': tag, 'error': exception - }) + messages.error( + request, _('Error deleting tag "%(tag)s": %(error)s') % { + 'tag': tag, 'error': exception + } + ) return HttpResponseRedirect(next) @@ -179,8 +221,10 @@ def tag_delete(request, tag_id=None, tag_id_list=None): if len(tags) == 1: context['object'] = tags[0] - return render_to_response('appearance/generic_confirm.html', context, - context_instance=RequestContext(request)) + return render_to_response( + 'appearance/generic_confirm.html', context, + context_instance=RequestContext(request) + ) def tag_multiple_delete(request): @@ -222,16 +266,27 @@ class DocumentTagListView(TagListView): 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(DocumentTagListView, self).dispatch(request, *args, **kwargs) def get_extra_context(self): return { 'extra_columns': [ - {'name': _('Documents'), 'attribute': encapsulate(lambda instance: TagListView.get_document_count(instance=instance, user=self.request.user))}, + { + 'name': _('Documents'), + 'attribute': encapsulate( + lambda instance: TagListView.get_document_count( + instance=instance, user=self.request.user + ) + ) + }, ], 'hide_link': True, 'object': self.document, @@ -248,13 +303,21 @@ def tag_remove(request, document_id=None, document_id_list=None, tag_id=None, ta elif document_id_list: 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 tagged document.')) - return HttpResponseRedirect(request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))) + messages.error( + request, _('Must provide at least one tagged document.') + ) + return HttpResponseRedirect( + request.META.get( + 'HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL) + ) + ) try: Permission.check_permissions(request.user, [permission_tag_remove]) except PermissionDenied: - documents = AccessControlList.objects.filter_by_access(permission_tag_remove, request.user, documents) + documents = AccessControlList.objects.filter_by_access( + permission_tag_remove, request.user, documents + ) post_action_redirect = None @@ -270,7 +333,11 @@ def tag_remove(request, document_id=None, document_id_list=None, tag_id=None, ta if tag_id: tags = [get_object_or_404(Tag, pk=tag_id)] elif tag_id_list: - tags = [get_object_or_404(Tag, pk=tag_id) for tag_id in tag_id_list.split(',')] + tags = [ + get_object_or_404( + Tag, pk=tag_id + ) for tag_id in tag_id_list.split(',') + ] else: template = 'appearance/generic_form.html' @@ -286,39 +353,66 @@ def tag_remove(request, document_id=None, document_id_list=None, tag_id=None, ta context['form'] = form if len(documents) == 1: context['object'] = documents[0] - context['title'] = _('Remove tag from document: %s.') % ', '.join([unicode(d) for d in documents]) + context['title'] = _( + 'Remove tag from document: %s.' + ) % ', '.join([unicode(d) for d in documents]) elif len(documents) > 1: - context['title'] = _('Remove tag from documents: %s.') % ', '.join([unicode(d) for d in documents]) + context['title'] = _( + 'Remove tag from documents: %s.' + ) % ', '.join([unicode(d) for d in documents]) if tags: if len(tags) == 1: if len(documents) == 1: context['object'] = documents[0] - context['title'] = _('Remove the tag "%(tag)s" from the document: %(document)s?') % { - 'tag': ', '.join([unicode(d) for d in tags]), 'document': ', '.join([unicode(d) for d in documents])} + context['title'] = _( + 'Remove the tag "%(tag)s" from the document: %(document)s?' + ) % { + 'tag': ', '.join([unicode(d) for d in tags]), + 'document': ', '.join([unicode(d) for d in documents]) + } else: - context['title'] = _('Remove the tag "%(tag)s" from the documents: %(documents)s?') % { - 'tag': ', '.join([unicode(d) for d in tags]), 'documents': ', '.join([unicode(d) for d in documents])} + context['title'] = _( + 'Remove the tag "%(tag)s" from the documents: %(documents)s?' + ) % { + 'tag': ', '.join([unicode(d) for d in tags]), + 'documents': ', '.join([unicode(d) for d in documents]) + } elif len(tags) > 1: if len(documents) == 1: context['object'] = documents[0] - context['title'] = _('Remove the tags: %(tags)s from the document: %(document)s?') % { - 'tags': ', '.join([unicode(d) for d in tags]), 'document': ', '.join([unicode(d) for d in documents])} + context['title'] = _( + 'Remove the tags: %(tags)s from the document: %(document)s?' + ) % { + 'tags': ', '.join([unicode(d) for d in tags]), + 'document': ', '.join([unicode(d) for d in documents]) + } else: - context['title'] = _('Remove the tags %(tags)s from the documents: %(documents)s?') % { - 'tags': ', '.join([unicode(d) for d in tags]), 'documents': ', '.join([unicode(d) for d in documents])} + context['title'] = _( + 'Remove the tags %(tags)s from the documents: %(documents)s?' + ) % { + 'tags': ', '.join([unicode(d) for d in tags]), + 'documents': ', '.join([unicode(d) for d in documents]) + } if request.method == 'POST': for document in documents: for tag in tags: if tag not in document.tags.all(): - messages.warning(request, _('Document "%(document)s" wasn\'t tagged as "%(tag)s"') % { - 'document': document, 'tag': tag} + messages.warning( + request, _('Document "%(document)s" wasn\'t tagged as "%(tag)s"' + ) % { + 'document': document, 'tag': tag + } ) else: tag.documents.remove(document) - messages.success(request, _('Tag "%(tag)s" removed successfully from document "%(document)s".') % { - 'document': document, 'tag': tag} + messages.success( + request, _( + 'Tag "%(tag)s" removed successfully from document "%(document)s".' + ) % { + 'document': document, 'tag': tag + } ) return HttpResponseRedirect(next) @@ -328,7 +422,10 @@ def tag_remove(request, document_id=None, document_id_list=None, tag_id=None, ta def single_document_multiple_tag_remove(request, document_id): - return tag_remove(request, document_id=document_id, tag_id_list=request.GET.get('id_list', [])) + return tag_remove( + request, document_id=document_id, + tag_id_list=request.GET.get('id_list', []) + ) def multiple_documents_selection_tag_remove(request): diff --git a/mayan/apps/user_management/apps.py b/mayan/apps/user_management/apps.py index 71a457122b..59e1fb262a 100644 --- a/mayan/apps/user_management/apps.py +++ b/mayan/apps/user_management/apps.py @@ -28,12 +28,39 @@ class UserManagementApp(MayanAppConfig): APIEndPoint('users', app_name='user_management') - menu_multi_item.bind_links(links=[link_group_multiple_delete], sources=['user_management:group_list']) - menu_multi_item.bind_links(links=[link_user_multiple_set_password, link_user_multiple_delete], sources=['user_management:user_list']) - menu_object.bind_links(links=[link_group_edit, link_group_members, link_group_delete], sources=[Group]) - menu_object.bind_links(links=[link_user_edit, link_user_set_password, link_user_groups, link_user_delete], sources=[User]) - menu_secondary.bind_links(links=[link_group_list, link_group_add], sources=['user_management:group_multiple_delete', 'user_management:group_delete', 'user_management:group_edit', 'user_management:group_list', 'user_management:group_add', 'user_management:group_members']) - menu_secondary.bind_links(links=[link_user_list, link_user_add], sources=[User, 'user_management:user_multiple_set_password', 'user_management:user_multiple_delete', 'user_management:user_list', 'user_management:user_add']) + menu_multi_item.bind_links( + links=[link_group_multiple_delete], + sources=['user_management:group_list'] + ) + menu_multi_item.bind_links( + links=[link_user_multiple_set_password, link_user_multiple_delete], + sources=['user_management:user_list'] + ) + menu_object.bind_links( + links=[link_group_edit, link_group_members, link_group_delete], + sources=[Group] + ) + menu_object.bind_links( + links=[ + link_user_edit, link_user_set_password, link_user_groups, + link_user_delete + ], sources=[User] + ) + menu_secondary.bind_links( + links=[link_group_list, link_group_add], sources=[ + 'user_management:group_multiple_delete', + 'user_management:group_delete', 'user_management:group_edit', + 'user_management:group_list', 'user_management:group_add', + 'user_management:group_members' + ] + ) + menu_secondary.bind_links( + links=[link_user_list, link_user_add], sources=[ + User, 'user_management:user_multiple_set_password', + 'user_management:user_multiple_delete', + 'user_management:user_list', 'user_management:user_add' + ] + ) menu_setup.bind_links(links=[link_user_setup, link_group_setup]) registry.register(Group) diff --git a/mayan/apps/user_management/forms.py b/mayan/apps/user_management/forms.py index c4dd673a9e..9b1eff1a2e 100644 --- a/mayan/apps/user_management/forms.py +++ b/mayan/apps/user_management/forms.py @@ -12,5 +12,9 @@ class UserForm(forms.ModelForm): class PasswordForm(forms.Form): - new_password_1 = forms.CharField(label=_('New password'), widget=forms.PasswordInput()) - new_password_2 = forms.CharField(label=_('Confirm password'), widget=forms.PasswordInput()) + new_password_1 = forms.CharField( + label=_('New password'), widget=forms.PasswordInput() + ) + new_password_2 = forms.CharField( + label=_('Confirm password'), widget=forms.PasswordInput() + ) diff --git a/mayan/apps/user_management/links.py b/mayan/apps/user_management/links.py index e0a70143c6..25da055765 100644 --- a/mayan/apps/user_management/links.py +++ b/mayan/apps/user_management/links.py @@ -10,19 +10,67 @@ from .permissions import ( permission_user_edit, permission_user_view ) -link_group_add = Link(permissions=[permission_group_create], text=_('Create new group'), view='user_management:group_add') -link_group_delete = Link(permissions=[permission_group_delete], tags='dangerous', text=_('Delete'), view='user_management:group_delete', args='object.id') -link_group_edit = Link(permissions=[permission_group_edit], text=_('Edit'), view='user_management:group_edit', args='object.id') -link_group_list = Link(permissions=[permission_group_view], text=_('Groups'), view='user_management:group_list') -link_group_members = Link(permissions=[permission_group_edit], text=_('Members'), view='user_management:group_members', args='object.id') -link_group_multiple_delete = Link(permissions=[permission_group_delete], text=_('Delete'), view='user_management:group_multiple_delete') -link_group_setup = Link(icon='fa fa-group', permissions=[permission_group_view], text=_('Groups'), view='user_management:group_list') -link_user_add = Link(permissions=[permission_user_create], text=_('Create new user'), view='user_management:user_add') -link_user_delete = Link(permissions=[permission_user_delete], tags='dangerous', text=_('Delete'), view='user_management:user_delete', args='object.id') -link_user_edit = Link(permissions=[permission_user_edit], text=_('Edit'), view='user_management:user_edit', args='object.id') -link_user_groups = Link(permissions=[permission_user_edit], text=_('Groups'), view='user_management:user_groups', args='object.id') -link_user_list = Link(permissions=[permission_user_view], text=_('Users'), view='user_management:user_list') -link_user_multiple_delete = Link(permissions=[permission_user_delete], tags='dangerous', text=_('Delete'), view='user_management:user_multiple_delete') -link_user_multiple_set_password = Link(permissions=[permission_user_edit], text=_('Reset password'), view='user_management:user_multiple_set_password') -link_user_set_password = Link(permissions=[permission_user_edit], text=_('Reset password'), view='user_management:user_set_password', args='object.id') -link_user_setup = Link(icon='fa fa-user', permissions=[permission_user_view], text=_('Users'), view='user_management:user_list') +link_group_add = Link( + permissions=[permission_group_create], text=_('Create new group'), + view='user_management:group_add' +) +link_group_delete = Link( + permissions=[permission_group_delete], tags='dangerous', text=_('Delete'), + view='user_management:group_delete', args='object.id' +) +link_group_edit = Link( + permissions=[permission_group_edit], text=_('Edit'), + view='user_management:group_edit', args='object.id' +) +link_group_list = Link( + permissions=[permission_group_view], text=_('Groups'), + view='user_management:group_list' +) +link_group_members = Link( + permissions=[permission_group_edit], text=_('Members'), + view='user_management:group_members', args='object.id' +) +link_group_multiple_delete = Link( + permissions=[permission_group_delete], text=_('Delete'), + view='user_management:group_multiple_delete' +) +link_group_setup = Link( + icon='fa fa-group', permissions=[permission_group_view], text=_('Groups'), + view='user_management:group_list' +) +link_user_add = Link( + permissions=[permission_user_create], text=_('Create new user'), + view='user_management:user_add' +) +link_user_delete = Link( + permissions=[permission_user_delete], tags='dangerous', text=_('Delete'), + view='user_management:user_delete', args='object.id' +) +link_user_edit = Link( + permissions=[permission_user_edit], text=_('Edit'), + view='user_management:user_edit', args='object.id' +) +link_user_groups = Link( + permissions=[permission_user_edit], text=_('Groups'), + view='user_management:user_groups', args='object.id' +) +link_user_list = Link( + permissions=[permission_user_view], text=_('Users'), + view='user_management:user_list' +) +link_user_multiple_delete = Link( + permissions=[permission_user_delete], tags='dangerous', text=_('Delete'), + view='user_management:user_multiple_delete' +) +link_user_multiple_set_password = Link( + permissions=[permission_user_edit], text=_('Reset password'), + view='user_management:user_multiple_set_password' +) +link_user_set_password = Link( + permissions=[permission_user_edit], text=_('Reset password'), + view='user_management:user_set_password', args='object.id' +) +link_user_setup = Link( + icon='fa fa-user', permissions=[permission_user_view], text=_('Users'), + view='user_management:user_list' +) diff --git a/mayan/apps/user_management/permissions.py b/mayan/apps/user_management/permissions.py index 9a6fc61616..e7a97d1540 100644 --- a/mayan/apps/user_management/permissions.py +++ b/mayan/apps/user_management/permissions.py @@ -6,11 +6,27 @@ from permissions import PermissionNamespace namespace = PermissionNamespace('user_management', _('User management')) -permission_group_create = namespace.add_permission(name='group_create', label=_('Create new groups')) -permission_group_delete = namespace.add_permission(name='group_delete', label=_('Delete existing groups')) -permission_group_edit = namespace.add_permission(name='group_edit', label=_('Edit existing groups')) -permission_group_view = namespace.add_permission(name='group_view', label=_('View existing groups')) -permission_user_create = namespace.add_permission(name='user_create', label=_('Create new users')) -permission_user_delete = namespace.add_permission(name='user_delete', label=_('Delete existing users')) -permission_user_edit = namespace.add_permission(name='user_edit', label=_('Edit existing users')) -permission_user_view = namespace.add_permission(name='user_view', label=_('View existing users')) +permission_group_create = namespace.add_permission( + name='group_create', label=_('Create new groups') +) +permission_group_delete = namespace.add_permission( + name='group_delete', label=_('Delete existing groups') +) +permission_group_edit = namespace.add_permission( + name='group_edit', label=_('Edit existing groups') +) +permission_group_view = namespace.add_permission( + name='group_view', label=_('View existing groups') +) +permission_user_create = namespace.add_permission( + name='user_create', label=_('Create new users') +) +permission_user_delete = namespace.add_permission( + name='user_delete', label=_('Delete existing users') +) +permission_user_edit = namespace.add_permission( + name='user_edit', label=_('Edit existing users') +) +permission_user_view = namespace.add_permission( + name='user_view', label=_('View existing users') +) diff --git a/mayan/apps/user_management/serializers.py b/mayan/apps/user_management/serializers.py index ba3b7565b7..2f62677a0b 100644 --- a/mayan/apps/user_management/serializers.py +++ b/mayan/apps/user_management/serializers.py @@ -7,7 +7,11 @@ from rest_framework import serializers class UserSerializer(serializers.ModelSerializer): class Meta: - fields = ('id', 'username', 'first_name', 'last_name', 'email', 'is_staff', 'is_active', 'is_superuser', 'last_login', 'date_joined', 'password') + fields = ( + 'id', 'username', 'first_name', 'last_name', 'email', 'is_staff', + 'is_active', 'is_superuser', 'last_login', 'date_joined', + 'password' + ) model = User read_only_fields = ('last_login', 'date_joined') write_only_fields = ('password',) diff --git a/mayan/apps/user_management/urls.py b/mayan/apps/user_management/urls.py index 52454bd3a4..fc26bb6520 100644 --- a/mayan/apps/user_management/urls.py +++ b/mayan/apps/user_management/urls.py @@ -15,19 +15,43 @@ urlpatterns = patterns( 'user_management.views', url(r'^group/list/$', GroupListView.as_view(), name='group_list'), url(r'^group/add/$', GroupCreateView.as_view(), name='group_add'), - url(r'^group/(?P\d+)/edit/$', GroupEditView.as_view(), name='group_edit'), - url(r'^group/(?P\d+)/delete/$', 'group_delete', name='group_delete'), - url(r'^group/multiple/delete/$', 'group_multiple_delete', name='group_multiple_delete'), - url(r'^group/(?P\d+)/members/$', GroupMembersView.as_view(), name='group_members'), + url( + r'^group/(?P\d+)/edit/$', GroupEditView.as_view(), + name='group_edit' + ), + url( + r'^group/(?P\d+)/delete/$', 'group_delete', + name='group_delete' + ), + url( + r'^group/multiple/delete/$', 'group_multiple_delete', + name='group_multiple_delete' + ), + url( + r'^group/(?P\d+)/members/$', GroupMembersView.as_view(), + name='group_members' + ), url(r'^user/list/$', UserListView.as_view(), name='user_list'), url(r'^user/add/$', 'user_add', name='user_add'), url(r'^user/(?P\d+)/edit/$', 'user_edit', name='user_edit'), url(r'^user/(?P\d+)/delete/$', 'user_delete', name='user_delete'), - url(r'^user/multiple/delete/$', 'user_multiple_delete', name='user_multiple_delete'), - url(r'^user/(?P\d+)/set_password/$', 'user_set_password', name='user_set_password'), - url(r'^user/multiple/set_password/$', 'user_multiple_set_password', name='user_multiple_set_password'), - url(r'^user/(?P\d+)/groups/$', UserGroupsView.as_view(), name='user_groups'), + url( + r'^user/multiple/delete/$', 'user_multiple_delete', + name='user_multiple_delete' + ), + url( + r'^user/(?P\d+)/set_password/$', 'user_set_password', + name='user_set_password' + ), + url( + r'^user/multiple/set_password/$', 'user_multiple_set_password', + name='user_multiple_set_password' + ), + url( + r'^user/(?P\d+)/groups/$', UserGroupsView.as_view(), + name='user_groups' + ), ) api_urls = patterns( diff --git a/mayan/apps/user_management/views.py b/mayan/apps/user_management/views.py index 585544bbfc..263d4f2261 100644 --- a/mayan/apps/user_management/views.py +++ b/mayan/apps/user_management/views.py @@ -30,7 +30,9 @@ class UserListView(SingleObjectListView): view_permission = permission_user_view def get_queryset(self): - return get_user_model().objects.exclude(is_superuser=True).exclude(is_staff=True).order_by('last_name', 'first_name') + return get_user_model().objects.exclude( + is_superuser=True + ).exclude(is_staff=True).order_by('last_name', 'first_name') def get_context_data(self, **kwargs): context = super(UserListView, self).get_context_data(**kwargs) @@ -49,11 +51,17 @@ class UserListView(SingleObjectListView): }, { 'name': _('Active'), - 'attribute': encapsulate(lambda x: two_state_template(x.is_active)), + 'attribute': encapsulate( + lambda user: two_state_template(user.is_active) + ), }, { 'name': _('Has usable password?'), - 'attribute': encapsulate(lambda x: two_state_template(x.has_usable_password())), + 'attribute': encapsulate( + lambda user: two_state_template( + user.has_usable_password() + ) + ), }, ], } @@ -67,14 +75,23 @@ def user_edit(request, user_id): user = get_object_or_404(User, pk=user_id) if user.is_superuser or user.is_staff: - messages.error(request, _('Super user and staff user editing is not allowed, use the admin interface for these cases.')) - return HttpResponseRedirect(request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))) + messages.error( + request, + _('Super user and staff user editing is not allowed, use the admin interface for these cases.') + ) + return HttpResponseRedirect( + request.META.get( + 'HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL) + ) + ) if request.method == 'POST': form = UserForm(instance=user, data=request.POST) if form.is_valid(): form.save() - messages.success(request, _('User "%s" updated successfully.') % user) + messages.success( + request, _('User "%s" updated successfully.') % user + ) return HttpResponseRedirect(reverse('user_management:user_list')) else: form = UserForm(instance=user) @@ -95,8 +112,12 @@ def user_add(request): user = form.save(commit=False) user.set_unusable_password() user.save() - messages.success(request, _('User "%s" created successfully.') % user) - return HttpResponseRedirect(reverse('user_management:user_set_password', args=[user.pk])) + messages.success( + request, _('User "%s" created successfully.') % user + ) + return HttpResponseRedirect( + reverse('user_management:user_set_password', args=[user.pk]) + ) else: form = UserForm() @@ -114,10 +135,16 @@ def user_delete(request, user_id=None, user_id_list=None): users = [get_object_or_404(User, pk=user_id)] post_action_redirect = reverse('user_management:user_list') elif user_id_list: - users = [get_object_or_404(User, pk=user_id) for user_id in user_id_list.split(',')] + users = [ + get_object_or_404(User, pk=user_id) for user_id in user_id_list.split(',') + ] else: messages.error(request, _('Must provide at least one user.')) - return HttpResponseRedirect(request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))) + return HttpResponseRedirect( + 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', post_action_redirect if post_action_redirect else request.META.get('HTTP_REFERER', reverse(settings.LOGIN_REDIRECT_URL))))