diff --git a/mayan/apps/checkouts/apps.py b/mayan/apps/checkouts/apps.py index 6f9e5b7c00..56041bdb25 100644 --- a/mayan/apps/checkouts/apps.py +++ b/mayan/apps/checkouts/apps.py @@ -6,7 +6,9 @@ from django.utils.translation import ugettext_lazy as _ from mayan.apps.acls.classes import ModelPermission from mayan.apps.common.apps import MayanAppConfig -from mayan.apps.common.menus import menu_facet, menu_main, menu_secondary +from mayan.apps.common.menus import ( + menu_facet, menu_main, menu_multi_item, menu_secondary +) from mayan.apps.dashboards.dashboards import dashboard_main from mayan.apps.events.classes import ModelEventType @@ -17,8 +19,9 @@ from .events import ( ) from .handlers import handler_check_new_version_creation from .links import ( - link_check_in_document, link_check_out_document, link_check_out_info, - link_check_out_list + link_check_in_document, link_check_in_document_multiple, + link_check_out_document, link_check_out_document_multiple, + link_check_out_info, link_check_out_list ) from .methods import ( method_check_in, method_get_check_out_info, method_get_check_out_state, @@ -85,6 +88,12 @@ class CheckoutsApp(MayanAppConfig): links=(link_check_out_info,), sources=(Document,) ) menu_main.bind_links(links=(link_check_out_list,), position=98) + menu_multi_item.bind_links( + links=( + link_check_in_document_multiple, + link_check_out_document_multiple + ), sources=(Document,) + ) menu_secondary.bind_links( links=(link_check_out_document, link_check_in_document), sources=( diff --git a/mayan/apps/checkouts/links.py b/mayan/apps/checkouts/links.py index d03d54c23d..c8012653c9 100644 --- a/mayan/apps/checkouts/links.py +++ b/mayan/apps/checkouts/links.py @@ -38,16 +38,26 @@ link_check_out_document = Link( args='object.pk', condition=is_not_checked_out, icon_class=icon_check_out_document, permissions=(permission_document_check_out,), - text=_('Check out document'), view='checkouts:check_out_document', + text=_('Check out document'), view='checkouts:check_out_document' +) +link_check_out_document_multiple = Link( + icon_class=icon_check_out_document, + permissions=(permission_document_check_out,), text=_('Check out'), + view='checkouts:check_out_document_multiple' ) link_check_in_document = Link( args='object.pk', icon_class=icon_check_in_document, condition=is_checked_out, permissions=( permission_document_check_in, permission_document_check_in_override - ), text=_('Check in document'), view='checkouts:check_in_document', + ), text=_('Check in document'), view='checkouts:check_in_document' +) +link_check_in_document_multiple = Link( + icon_class=icon_check_in_document, + permissions=(permission_document_check_in,), text=_('Check in'), + view='checkouts:check_in_document_multiple' ) link_check_out_info = Link( args='resolved_object.pk', icon_class=icon_check_out_info, permissions=( permission_document_check_out_detail_view, - ), text=_('Check in/out'), view='checkouts:check_out_info', + ), text=_('Check in/out'), view='checkouts:check_out_info' ) diff --git a/mayan/apps/checkouts/tests/mixins.py b/mayan/apps/checkouts/tests/mixins.py index fa8d601a4b..2bf2041ab8 100644 --- a/mayan/apps/checkouts/tests/mixins.py +++ b/mayan/apps/checkouts/tests/mixins.py @@ -4,6 +4,8 @@ import datetime from django.utils.timezone import now +from mayan.apps.common.literals import TIME_DELTA_UNIT_DAYS + from ..models import DocumentCheckout @@ -23,3 +25,40 @@ class DocumentCheckoutTestMixin(object): expiration_datetime=self._check_out_expiration_datetime, user=user ) + + +class DocumentCheckoutViewTestMixin(object): + def _request_test_document_check_in_get_view(self): + return self.get( + viewname='checkouts:check_in_document', kwargs={ + 'pk': self.test_document.pk + } + ) + + def _request_test_document_check_in_post_view(self): + return self.post( + viewname='checkouts:check_in_document', kwargs={ + 'pk': self.test_document.pk + } + ) + + def _request_test_document_check_out_view(self): + return self.post( + viewname='checkouts:check_out_document', kwargs={ + 'pk': self.test_document.pk + }, data={ + 'block_new_version': True, + 'expiration_datetime_0': TIME_DELTA_UNIT_DAYS, + 'expiration_datetime_1': 2 + } + ) + + def _request_test_document_check_out_detail_view(self): + return self.get( + viewname='checkouts:check_out_info', kwargs={ + 'pk': self.test_document.pk + } + ) + + def _request_test_document_check_out_list_view(self): + return self.get(viewname='checkouts:check_out_list') diff --git a/mayan/apps/checkouts/tests/test_api.py b/mayan/apps/checkouts/tests/test_api.py index 71ec5ea536..d87bae3c17 100644 --- a/mayan/apps/checkouts/tests/test_api.py +++ b/mayan/apps/checkouts/tests/test_api.py @@ -65,7 +65,7 @@ class CheckoutsAPITestCase(DocumentCheckoutTestMixin, DocumentTestMixin, BaseAPI force_text(self.test_document.uuid) ) - def _request_document_checkout_view(self): + def _request_test_document_check_out_view(self): return self.post( viewname='rest_api:checkout-document-list', data={ 'document_pk': self.test_document.pk, @@ -74,7 +74,7 @@ class CheckoutsAPITestCase(DocumentCheckoutTestMixin, DocumentTestMixin, BaseAPI ) def test_document_checkout_no_access(self): - response = self._request_document_checkout_view() + response = self._request_test_document_check_out_view() self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) self.assertEqual(DocumentCheckout.objects.count(), 0) @@ -82,7 +82,7 @@ class CheckoutsAPITestCase(DocumentCheckoutTestMixin, DocumentTestMixin, BaseAPI def test_document_checkout_with_access(self): self.grant_access(permission=permission_document_check_out, obj=self.test_document) - response = self._request_document_checkout_view() + response = self._request_test_document_check_out_view() self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual( diff --git a/mayan/apps/checkouts/tests/test_views.py b/mayan/apps/checkouts/tests/test_views.py index 6b4648145f..ccfbef95af 100644 --- a/mayan/apps/checkouts/tests/test_views.py +++ b/mayan/apps/checkouts/tests/test_views.py @@ -12,64 +12,53 @@ from ..permissions import ( permission_document_check_out, permission_document_check_out_detail_view ) -from .mixins import DocumentCheckoutTestMixin +from .mixins import DocumentCheckoutTestMixin, DocumentCheckoutViewTestMixin -class DocumentCheckoutViewTestCase(DocumentCheckoutTestMixin, GenericDocumentViewTestCase): - def _request_document_check_in_get_view(self): - return self.get( - viewname='checkouts:check_in_document', kwargs={ - 'pk': self.test_document.pk - } - ) - - def test_check_in_document_get_view_no_permission(self): +class DocumentCheckoutViewTestCase( + DocumentCheckoutTestMixin, DocumentCheckoutViewTestMixin, + GenericDocumentViewTestCase +): + def test_document_check_in_get_view_no_permission(self): self._check_out_test_document() - response = self._request_document_check_in_get_view() + response = self._request_test_document_check_in_get_view() self.assertContains( response=response, text=self.test_document.label, status_code=200 ) self.assertTrue(self.test_document.is_checked_out()) - def test_check_in_document_get_view_with_access(self): + def test_document_check_in_get_view_with_access(self): self._check_out_test_document() self.grant_access( obj=self.test_document, permission=permission_document_check_in ) - response = self._request_document_check_in_get_view() + response = self._request_test_document_check_in_get_view() self.assertContains( response=response, text=self.test_document.label, status_code=200 ) self.assertTrue(self.test_document.is_checked_out()) - def _request_document_check_in_post_view(self): - return self.post( - viewname='checkouts:check_in_document', kwargs={ - 'pk': self.test_document.pk - } - ) - - def test_check_in_document_post_view_no_permission(self): + def test_document_check_in_post_view_no_permission(self): self._check_out_test_document() - response = self._request_document_check_in_post_view() - self.assertEqual(response.status_code, 403) + response = self._request_test_document_check_in_post_view() + self.assertEqual(response.status_code, 404) self.assertTrue(self.test_document.is_checked_out()) - def test_check_in_document_post_view_with_access(self): + def test_document_check_in_post_view_with_access(self): self._check_out_test_document() self.grant_access( obj=self.test_document, permission=permission_document_check_in ) - response = self._request_document_check_in_post_view() + response = self._request_test_document_check_in_post_view() self.assertEqual(response.status_code, 302) self.assertFalse(self.test_document.is_checked_out()) @@ -79,24 +68,13 @@ class DocumentCheckoutViewTestCase(DocumentCheckoutTestMixin, GenericDocumentVie ) ) - def _request_document_checkout_view(self): - return self.post( - viewname='checkouts:check_out_document', kwargs={ - 'pk': self.test_document.pk - }, data={ - 'expiration_datetime_0': 2, - 'expiration_datetime_1': TIME_DELTA_UNIT_DAYS, - 'block_new_version': True - } - ) - - def test_check_out_document_view_no_permission(self): - response = self._request_document_checkout_view() - self.assertEqual(response.status_code, 403) + def test_document_check_out_view_no_permission(self): + response = self._request_test_document_check_out_view() + self.assertEqual(response.status_code, 404) self.assertFalse(self.test_document.is_checked_out()) - def test_check_out_document_view_with_access(self): + def test_document_check_out_view_with_access(self): self.grant_access( obj=self.test_document, permission=permission_document_check_out ) @@ -105,28 +83,21 @@ class DocumentCheckoutViewTestCase(DocumentCheckoutTestMixin, GenericDocumentVie permission=permission_document_check_out_detail_view ) - response = self._request_document_checkout_view() + response = self._request_test_document_check_out_view() self.assertEqual(response.status_code, 302) self.assertTrue(self.test_document.is_checked_out()) - def _request_check_out_detail_view(self): - return self.get( - viewname='checkouts:check_out_info', kwargs={ - 'pk': self.test_document.pk - } - ) - - def test_checkout_detail_view_no_permission(self): + def test_document_check_out_detail_view_no_permission(self): self._check_out_test_document() - response = self._request_check_out_detail_view() + response = self._request_test_document_check_out_detail_view() self.assertNotContains( response, text=STATE_LABELS[STATE_CHECKED_OUT], status_code=404 ) - def test_checkout_detail_view_with_access(self): + def test_document_check_out_detail_view_with_access(self): self._check_out_test_document() self.grant_access( @@ -134,15 +105,12 @@ class DocumentCheckoutViewTestCase(DocumentCheckoutTestMixin, GenericDocumentVie permission=permission_document_check_out_detail_view ) - response = self._request_check_out_detail_view() + response = self._request_test_document_check_out_detail_view() self.assertContains( response, text=STATE_LABELS[STATE_CHECKED_OUT], status_code=200 ) - def _request_check_out_list_view(self): - return self.get(viewname='checkouts:check_out_list') - - def test_checkout_list_view_no_permission(self): + def test_document_checkout_list_view_no_permission(self): self._check_out_test_document() self.grant_access( @@ -150,12 +118,12 @@ class DocumentCheckoutViewTestCase(DocumentCheckoutTestMixin, GenericDocumentVie permission=permission_document_view ) - response = self._request_check_out_list_view() + response = self._request_test_document_check_out_list_view() self.assertNotContains( response=response, text=self.test_document.label, status_code=200 ) - def test_checkout_list_view_with_access(self): + def test_document_checkout_list_view_with_access(self): self._check_out_test_document() self.grant_access( @@ -167,12 +135,12 @@ class DocumentCheckoutViewTestCase(DocumentCheckoutTestMixin, GenericDocumentVie permission=permission_document_view ) - response = self._request_check_out_list_view() + response = self._request_test_document_check_out_list_view() self.assertContains( response=response, text=self.test_document.label, status_code=200 ) - def test_document_new_version_after_check_out(self): + def test_document_check_out_new_version(self): """ Gitlab issue #231 User shown option to upload new version of a document even though it @@ -209,7 +177,7 @@ class DocumentCheckoutViewTestCase(DocumentCheckoutTestMixin, GenericDocumentVie self.assertEqual(resolved_link, None) - def test_forcefull_check_in_document_view_no_permission(self): + def test_document_forcefull_check_in_view_no_permission(self): # Gitlab issue #237 # Forcefully checking in a document by a user without adequate # permissions throws out an error @@ -232,7 +200,7 @@ class DocumentCheckoutViewTestCase(DocumentCheckoutTestMixin, GenericDocumentVie self.assertTrue(self.test_document.is_checked_out()) - def test_forcefull_check_in_document_view_with_permission(self): + def test_document_forcefull_check_in_view_with_permission(self): self._create_test_case_superuser() self._check_out_test_document(user=self._test_case_superuser) diff --git a/mayan/apps/checkouts/urls.py b/mayan/apps/checkouts/urls.py index 994d18edde..0e30989cf4 100644 --- a/mayan/apps/checkouts/urls.py +++ b/mayan/apps/checkouts/urls.py @@ -4,25 +4,34 @@ from django.conf.urls import url from .api_views import APICheckedoutDocumentListView, APICheckedoutDocumentView from .views import ( - CheckoutDocumentView, CheckoutDetailView, CheckoutListView, - DocumentCheckinView + DocumentCheckinView, DocumentCheckoutDetailView, DocumentCheckoutView, + DocumentCheckoutListView ) urlpatterns = [ url( - regex=r'^list/$', view=CheckoutListView.as_view(), name='check_out_list' + regex=r'^documents/$', view=DocumentCheckoutListView.as_view(), + name='check_out_list' ), url( - regex=r'^(?P\d+)/check/out/$', view=CheckoutDocumentView.as_view(), - name='check_out_document' - ), - url( - regex=r'^(?P\d+)/check/in/$', view=DocumentCheckinView.as_view(), + regex=r'^documents/(?P\d+)/check_in/$', view=DocumentCheckinView.as_view(), name='check_in_document' ), url( - regex=r'^(?P\d+)/check/info/$', view=CheckoutDetailView.as_view(), - name='check_out_info' + regex=r'^documents/multiple/check_in/$', + name='check_in_document_multiple', view=DocumentCheckinView.as_view() + ), + url( + regex=r'^documents/(?P\d+)/check_out/$', view=DocumentCheckoutView.as_view(), + name='check_out_document' + ), + url( + regex=r'^documents/multiple/check_out/$', + name='check_out_document_multiple', view=DocumentCheckoutView.as_view() + ), + url( + regex=r'^documents/(?P\d+)/checkout/info/$', + view=DocumentCheckoutDetailView.as_view(), name='check_out_info' ), ] diff --git a/mayan/apps/checkouts/views.py b/mayan/apps/checkouts/views.py index 65063d2cfe..93f54a8190 100644 --- a/mayan/apps/checkouts/views.py +++ b/mayan/apps/checkouts/views.py @@ -4,11 +4,12 @@ from django.contrib import messages from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.urls import reverse -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _, ungettext from mayan.apps.acls.models import AccessControlList from mayan.apps.common.generics import ( - ConfirmView, SingleObjectCreateView, SingleObjectDetailView + ConfirmView, MultipleObjectConfirmActionView, MultipleObjectFormActionView, + SingleObjectCreateView, SingleObjectDetailView ) from mayan.apps.common.utils import encapsulate from mayan.apps.documents.models import Document @@ -24,6 +25,7 @@ from .permissions import ( ) +""" class DocumentCheckinView(ConfirmView): def get_extra_context(self): document = self.get_object() @@ -80,8 +82,59 @@ class DocumentCheckinView(ConfirmView): 'Document "%s" checked in successfully.' ) % document, request=self.request ) +""" + +class DocumentCheckinView(MultipleObjectConfirmActionView): + error_message = 'Unable to check in document "%(instance)s". %(exception)s' + model = Document + object_permission = permission_document_check_in + pk_url_kwarg = 'pk' + success_message_singular = '%(count)d document checked in.' + success_message_plural = '%(count)d documents checked in.' + + def get_extra_context(self): + queryset = self.get_object_list() + + result = { + 'title': ungettext( + singular='Check in %(count)d document', + plural='Check in %(count)d documents', + number=queryset.count() + ) % { + 'count': queryset.count(), + } + } + + if queryset.count() == 1: + result.update( + { + 'object': queryset.first(), + 'title': _( + 'Check in document: %s' + ) % queryset.first() + } + ) + + return result + + def get_post_object_action_url(self): + if self.action_count == 1: + return reverse( + viewname='checkouts:document_checkout_info', + kwargs={'pk': self.action_id_list[0]} + ) + else: + super(DocumentCheckinView, self).get_post_action_redirect() + + def object_action(self, form, instance): + DocumentCheckout.objects.check_in_document( + document=instance, user=self.request.user + ) + + +""" class CheckoutDocumentView(SingleObjectCreateView): form_class = DocumentCheckoutForm @@ -129,9 +182,74 @@ class CheckoutDocumentView(SingleObjectCreateView): 'pk': self.document.pk } ) +""" +class DocumentCheckoutView(MultipleObjectFormActionView): + error_message = 'Unable to checkout document "%(instance)s". %(exception)s' + form_class = DocumentCheckoutForm + model = Document + object_permission = permission_document_check_out + pk_url_kwarg = 'pk' + success_message_singular = '%(count)d document checked out.' + success_message_plural = '%(count)d documents checked out.' + + def get_extra_context(self): + queryset = self.get_object_list() + + result = { + 'title': ungettext( + singular='Checkout %(count)d document', + plural='Checkout %(count)d documents', + number=queryset.count() + ) % { + 'count': queryset.count(), + } + } + + if queryset.count() == 1: + result.update( + { + 'object': queryset.first(), + 'title': _( + 'Check out document: %s' + ) % queryset.first() + } + ) + + return result + + def get_post_object_action_url(self): + if self.action_count == 1: + return reverse( + viewname='checkouts:document_checkout_info', + kwargs={'pk': self.action_id_list[0]} + ) + else: + super(DocumentCheckoutView, self).get_post_action_redirect() + + def object_action(self, form, instance): + DocumentCheckout.objects.check_out_document( + block_new_version=form.cleaned_data['block_new_version'], + document=instance, + expiration_datetime=form.cleaned_data['expiration_datetime'], + user=self.request.user, + ) -class CheckoutListView(DocumentListView): +class DocumentCheckoutDetailView(SingleObjectDetailView): + form_class = DocumentCheckoutDefailForm + model = Document + object_permission = permission_document_check_out_detail_view + + def get_extra_context(self): + return { + 'object': self.object, + 'title': _( + 'Check out details for document: %s' + ) % self.object + } + + +class DocumentCheckoutListView(DocumentListView): def get_document_queryset(self): return AccessControlList.objects.restrict_queryset( permission=permission_document_check_out_detail_view, @@ -140,7 +258,7 @@ class CheckoutListView(DocumentListView): ) def get_extra_context(self): - context = super(CheckoutListView, self).get_extra_context() + context = super(DocumentCheckoutListView, self).get_extra_context() context.update( { 'extra_columns': ( @@ -165,26 +283,11 @@ class CheckoutListView(DocumentListView): ), 'no_results_icon': icon_check_out_info, 'no_results_text': _( - 'Checking out a document blocks certain document ' - 'operations for a predetermined amount of ' - 'time.' + 'Checking out a document, blocks certain operations ' + 'for a predetermined amount of time.' ), 'no_results_title': _('No documents have been checked out'), - 'title': _('Documents checked out'), + 'title': _('Checked out documents'), } ) return context - - -class CheckoutDetailView(SingleObjectDetailView): - form_class = DocumentCheckoutDefailForm - model = Document - object_permission = permission_document_check_out_detail_view - - def get_extra_context(self): - return { - 'object': self.object, - 'title': _( - 'Check out details for document: %s' - ) % self.object - } diff --git a/mayan/apps/checkouts/widgets.py b/mayan/apps/checkouts/widgets.py index 72497c8fba..5206cc662e 100644 --- a/mayan/apps/checkouts/widgets.py +++ b/mayan/apps/checkouts/widgets.py @@ -32,8 +32,8 @@ class SplitTimeDeltaWidget(forms.widgets.MultiWidget): return (None, None) def value_from_datadict(self, querydict, files, name): - unit = querydict.get('{}_1'.format(name)) - period = querydict.get('{}_0'.format(name)) + unit = querydict.get('{}_0'.format(name)) + period = querydict.get('{}_1'.format(name)) if not unit or not period: return now()