Complete multiple check in/out support
Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
This commit is contained in:
@@ -11,6 +11,7 @@ from mayan.apps.common.menus import (
|
|||||||
)
|
)
|
||||||
from mayan.apps.dashboards.dashboards import dashboard_main
|
from mayan.apps.dashboards.dashboards import dashboard_main
|
||||||
from mayan.apps.events.classes import ModelEventType
|
from mayan.apps.events.classes import ModelEventType
|
||||||
|
from mayan.apps.navigation.classes import SourceColumn
|
||||||
|
|
||||||
from .dashboard_widgets import DashboardWidgetTotalCheckouts
|
from .dashboard_widgets import DashboardWidgetTotalCheckouts
|
||||||
from .events import (
|
from .events import (
|
||||||
@@ -46,6 +47,8 @@ class CheckoutsApp(MayanAppConfig):
|
|||||||
def ready(self):
|
def ready(self):
|
||||||
super(CheckoutsApp, self).ready()
|
super(CheckoutsApp, self).ready()
|
||||||
|
|
||||||
|
CheckedOutDocument = self.get_model(model_name='CheckedOutDocument')
|
||||||
|
DocumentCheckout = self.get_model(model_name='DocumentCheckout')
|
||||||
Document = apps.get_model(
|
Document = apps.get_model(
|
||||||
app_label='documents', model_name='Document'
|
app_label='documents', model_name='Document'
|
||||||
)
|
)
|
||||||
@@ -79,6 +82,22 @@ class CheckoutsApp(MayanAppConfig):
|
|||||||
permission_document_check_out_detail_view
|
permission_document_check_out_detail_view
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
ModelPermission.register_inheritance(
|
||||||
|
model=DocumentCheckout, related='document'
|
||||||
|
)
|
||||||
|
|
||||||
|
SourceColumn(
|
||||||
|
attribute='get_user_display', include_label=True, order=99,
|
||||||
|
source=CheckedOutDocument
|
||||||
|
)
|
||||||
|
SourceColumn(
|
||||||
|
attribute='get_checkout_datetime', include_label=True, order=99,
|
||||||
|
source=CheckedOutDocument
|
||||||
|
)
|
||||||
|
SourceColumn(
|
||||||
|
attribute='get_checkout_expiration', include_label=True, order=99,
|
||||||
|
source=CheckedOutDocument
|
||||||
|
)
|
||||||
|
|
||||||
dashboard_main.add_widget(
|
dashboard_main.add_widget(
|
||||||
widget=DashboardWidgetTotalCheckouts, order=-1
|
widget=DashboardWidgetTotalCheckouts, order=-1
|
||||||
@@ -91,9 +110,19 @@ class CheckoutsApp(MayanAppConfig):
|
|||||||
menu_multi_item.bind_links(
|
menu_multi_item.bind_links(
|
||||||
links=(
|
links=(
|
||||||
link_check_in_document_multiple,
|
link_check_in_document_multiple,
|
||||||
link_check_out_document_multiple
|
), sources=(CheckedOutDocument,)
|
||||||
|
)
|
||||||
|
menu_multi_item.bind_links(
|
||||||
|
links=(
|
||||||
|
link_check_in_document_multiple,
|
||||||
|
link_check_out_document_multiple,
|
||||||
), sources=(Document,)
|
), sources=(Document,)
|
||||||
)
|
)
|
||||||
|
menu_multi_item.unbind_links(
|
||||||
|
links=(
|
||||||
|
link_check_out_document_multiple,
|
||||||
|
), sources=(CheckedOutDocument,)
|
||||||
|
)
|
||||||
menu_secondary.bind_links(
|
menu_secondary.bind_links(
|
||||||
links=(link_check_out_document, link_check_in_document),
|
links=(link_check_out_document, link_check_in_document),
|
||||||
sources=(
|
sources=(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from django.apps import apps
|
|||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
|
from mayan.apps.acls.models import AccessControlList
|
||||||
from mayan.apps.documents.models import Document
|
from mayan.apps.documents.models import Document
|
||||||
|
|
||||||
from .events import (
|
from .events import (
|
||||||
@@ -14,10 +15,53 @@ from .events import (
|
|||||||
)
|
)
|
||||||
from .exceptions import DocumentNotCheckedOut
|
from .exceptions import DocumentNotCheckedOut
|
||||||
from .literals import STATE_CHECKED_OUT, STATE_CHECKED_IN
|
from .literals import STATE_CHECKED_OUT, STATE_CHECKED_IN
|
||||||
|
from .permissions import (
|
||||||
|
permission_document_check_in, permission_document_check_in_override
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentCheckoutBusinessLogicManager(models.Manager):
|
||||||
|
def check_in_document(self, document, user=None):
|
||||||
|
queryset = document._meta.default_manager.filter(pk=document.pk)
|
||||||
|
return self.check_in_documents(queryset=queryset, user=user)
|
||||||
|
|
||||||
|
def check_in_documents(self, queryset, user=None):
|
||||||
|
if user:
|
||||||
|
user_document_checkouts = AccessControlList.objects.restrict_queryset(
|
||||||
|
permission=permission_document_check_in,
|
||||||
|
queryset=self.filter(user_id=user.pk, document__in=queryset),
|
||||||
|
user=user
|
||||||
|
)
|
||||||
|
|
||||||
|
others_document_checkouts = AccessControlList.objects.restrict_queryset(
|
||||||
|
permission=permission_document_check_in_override,
|
||||||
|
queryset=self.exclude(user_id=user.pk, document__in=queryset),
|
||||||
|
user=user
|
||||||
|
)
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
if user:
|
||||||
|
for checkout in user_document_checkouts:
|
||||||
|
event_document_check_in.commit(
|
||||||
|
actor=user, target=checkout.document
|
||||||
|
)
|
||||||
|
checkout.delete()
|
||||||
|
|
||||||
|
for checkout in others_document_checkouts:
|
||||||
|
event_document_forceful_check_in.commit(
|
||||||
|
actor=user, target=checkout.document
|
||||||
|
)
|
||||||
|
checkout.delete()
|
||||||
|
else:
|
||||||
|
for checkout in self.filter(document__in=queryset):
|
||||||
|
event_document_auto_check_in.commit(
|
||||||
|
target=checkout.document
|
||||||
|
)
|
||||||
|
checkout.delete()
|
||||||
|
|
||||||
|
|
||||||
class DocumentCheckoutManager(models.Manager):
|
class DocumentCheckoutManager(models.Manager):
|
||||||
def are_document_new_versions_allowed(self, document, user=None):
|
def are_document_new_versions_allowed(self, document, user=None):
|
||||||
try:
|
try:
|
||||||
@@ -27,25 +71,6 @@ class DocumentCheckoutManager(models.Manager):
|
|||||||
else:
|
else:
|
||||||
return not check_out_info.block_new_version
|
return not check_out_info.block_new_version
|
||||||
|
|
||||||
def check_in_document(self, document, user=None):
|
|
||||||
try:
|
|
||||||
document_check_out = self.model.objects.get(document=document)
|
|
||||||
except self.model.DoesNotExist:
|
|
||||||
raise DocumentNotCheckedOut
|
|
||||||
else:
|
|
||||||
with transaction.atomic():
|
|
||||||
if user:
|
|
||||||
if self.get_check_out_info(document=document).user != user:
|
|
||||||
event_document_forceful_check_in.commit(
|
|
||||||
actor=user, target=document
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
event_document_check_in.commit(actor=user, target=document)
|
|
||||||
else:
|
|
||||||
event_document_auto_check_in.commit(target=document)
|
|
||||||
|
|
||||||
document_check_out.delete()
|
|
||||||
|
|
||||||
def check_in_expired_check_outs(self):
|
def check_in_expired_check_outs(self):
|
||||||
for document in self.expired_check_outs():
|
for document in self.expired_check_outs():
|
||||||
document.check_in()
|
document.check_in()
|
||||||
@@ -57,7 +82,11 @@ class DocumentCheckoutManager(models.Manager):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def checked_out_documents(self):
|
def checked_out_documents(self):
|
||||||
return Document.objects.filter(
|
CheckedOutDocument = apps.get_model(
|
||||||
|
app_label='checkouts', model_name='CheckedOutDocument'
|
||||||
|
)
|
||||||
|
|
||||||
|
return CheckedOutDocument.objects.filter(
|
||||||
pk__in=self.model.objects.values('document__id')
|
pk__in=self.model.objects.values('document__id')
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -74,7 +103,11 @@ class DocumentCheckoutManager(models.Manager):
|
|||||||
return STATE_CHECKED_IN
|
return STATE_CHECKED_IN
|
||||||
|
|
||||||
def expired_check_outs(self):
|
def expired_check_outs(self):
|
||||||
expired_list = Document.objects.filter(
|
CheckedOutDocument = apps.get_model(
|
||||||
|
app_label='checkouts', model_name='CheckedOutDocument'
|
||||||
|
)
|
||||||
|
|
||||||
|
expired_list = CheckedOutDocument.objects.filter(
|
||||||
pk__in=self.model.objects.filter(
|
pk__in=self.model.objects.filter(
|
||||||
expiration_datetime__lte=now()
|
expiration_datetime__lte=now()
|
||||||
).values_list('document__pk', flat=True)
|
).values_list('document__pk', flat=True)
|
||||||
@@ -83,9 +116,6 @@ class DocumentCheckoutManager(models.Manager):
|
|||||||
return expired_list
|
return expired_list
|
||||||
|
|
||||||
def get_by_natural_key(self, document_natural_key):
|
def get_by_natural_key(self, document_natural_key):
|
||||||
Document = apps.get_model(
|
|
||||||
app_label='documents', model_name='Document'
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
document = Document.objects.get_by_natural_key(document_natural_key)
|
document = Document.objects.get_by_natural_key(document_natural_key)
|
||||||
except Document.DoesNotExist:
|
except Document.DoesNotExist:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ def method_check_in(self, user=None):
|
|||||||
app_label='checkouts', model_name='DocumentCheckout'
|
app_label='checkouts', model_name='DocumentCheckout'
|
||||||
)
|
)
|
||||||
|
|
||||||
return DocumentCheckout.objects.check_in_document(
|
return DocumentCheckout.business_logic.check_in_document(
|
||||||
document=self, user=user
|
document=self, user=user
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ from mayan.apps.documents.models import Document
|
|||||||
|
|
||||||
from .events import event_document_check_out
|
from .events import event_document_check_out
|
||||||
from .exceptions import DocumentAlreadyCheckedOut
|
from .exceptions import DocumentAlreadyCheckedOut
|
||||||
from .managers import DocumentCheckoutManager, NewVersionBlockManager
|
from .managers import (
|
||||||
|
DocumentCheckoutBusinessLogicManager, DocumentCheckoutManager,
|
||||||
|
NewVersionBlockManager
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -49,6 +52,7 @@ class DocumentCheckout(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
objects = DocumentCheckoutManager()
|
objects = DocumentCheckoutManager()
|
||||||
|
business_logic = DocumentCheckoutBusinessLogicManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('pk',)
|
ordering = ('pk',)
|
||||||
@@ -81,13 +85,13 @@ class DocumentCheckout(models.Model):
|
|||||||
natural_key.dependencies = ['documents.Document']
|
natural_key.dependencies = ['documents.Document']
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
new_checkout = not self.pk
|
is_new = not self.pk
|
||||||
if not new_checkout or self.document.is_checked_out():
|
if not is_new or self.document.is_checked_out():
|
||||||
raise DocumentAlreadyCheckedOut
|
raise DocumentAlreadyCheckedOut
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
result = super(DocumentCheckout, self).save(*args, **kwargs)
|
result = super(DocumentCheckout, self).save(*args, **kwargs)
|
||||||
if new_checkout:
|
if is_new:
|
||||||
event_document_check_out.commit(
|
event_document_check_out.commit(
|
||||||
actor=self.user, target=self.document
|
actor=self.user, target=self.document
|
||||||
)
|
)
|
||||||
@@ -119,3 +123,24 @@ class NewVersionBlock(models.Model):
|
|||||||
def natural_key(self):
|
def natural_key(self):
|
||||||
return self.document.natural_key()
|
return self.document.natural_key()
|
||||||
natural_key.dependencies = ['documents.Document']
|
natural_key.dependencies = ['documents.Document']
|
||||||
|
|
||||||
|
|
||||||
|
class CheckedOutDocument(Document):
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
|
||||||
|
def get_user_display(self):
|
||||||
|
check_out_info = self.get_check_out_info()
|
||||||
|
return check_out_info.user.get_full_name() or check_out_info.user
|
||||||
|
|
||||||
|
get_user_display.short_description = _('User')
|
||||||
|
|
||||||
|
def get_checkout_datetime(self):
|
||||||
|
return self.get_check_out_info().checkout_datetime
|
||||||
|
|
||||||
|
get_checkout_datetime.short_description = _('Checkout time and date')
|
||||||
|
|
||||||
|
def get_checkout_expiration(self):
|
||||||
|
return self.get_check_out_info().expiration_datetime
|
||||||
|
|
||||||
|
get_checkout_expiration.short_description = _('Checkout expiration')
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import datetime
|
|||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
from mayan.apps.common.literals import TIME_DELTA_UNIT_DAYS
|
from mayan.apps.common.literals import TIME_DELTA_UNIT_DAYS
|
||||||
|
from mayan.apps.common.tests.utils import as_id_list
|
||||||
|
|
||||||
from ..models import DocumentCheckout
|
from ..models import DocumentCheckout
|
||||||
|
|
||||||
@@ -12,7 +13,10 @@ from ..models import DocumentCheckout
|
|||||||
class DocumentCheckoutTestMixin(object):
|
class DocumentCheckoutTestMixin(object):
|
||||||
_test_document_check_out_seconds = 0.1
|
_test_document_check_out_seconds = 0.1
|
||||||
|
|
||||||
def _check_out_test_document(self, user=None):
|
def _check_out_test_document(self, document=None, user=None):
|
||||||
|
if not document:
|
||||||
|
document = self.test_document
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
user = self._test_case_user
|
user = self._test_case_user
|
||||||
|
|
||||||
@@ -21,7 +25,7 @@ class DocumentCheckoutTestMixin(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.test_check_out = DocumentCheckout.objects.check_out_document(
|
self.test_check_out = DocumentCheckout.objects.check_out_document(
|
||||||
block_new_version=True, document=self.test_document,
|
block_new_version=True, document=document,
|
||||||
expiration_datetime=self._check_out_expiration_datetime,
|
expiration_datetime=self._check_out_expiration_datetime,
|
||||||
user=user
|
user=user
|
||||||
)
|
)
|
||||||
@@ -42,6 +46,13 @@ class DocumentCheckoutViewTestMixin(object):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _request_test_document_multiple_check_in_post_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='checkouts:check_in_document_multiple', data={
|
||||||
|
'id_list': as_id_list(items=self.test_documents)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def _request_test_document_check_out_view(self):
|
def _request_test_document_check_out_view(self):
|
||||||
return self.post(
|
return self.post(
|
||||||
viewname='checkouts:check_out_document', kwargs={
|
viewname='checkouts:check_out_document', kwargs={
|
||||||
@@ -53,6 +64,16 @@ class DocumentCheckoutViewTestMixin(object):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _request_test_document_multiple_check_out_post_view(self):
|
||||||
|
return self.post(
|
||||||
|
viewname='checkouts:check_out_document_multiple', data={
|
||||||
|
'block_new_version': True,
|
||||||
|
'expiration_datetime_0': TIME_DELTA_UNIT_DAYS,
|
||||||
|
'expiration_datetime_1': 2,
|
||||||
|
'id_list': as_id_list(items=self.test_documents)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
def _request_test_document_check_out_detail_view(self):
|
def _request_test_document_check_out_detail_view(self):
|
||||||
return self.get(
|
return self.get(
|
||||||
viewname='checkouts:check_out_info', kwargs={
|
viewname='checkouts:check_out_info', kwargs={
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ from mayan.apps.documents.tests import GenericDocumentTestCase, DocumentTestMixi
|
|||||||
from mayan.apps.documents.tests.literals import TEST_SMALL_DOCUMENT_PATH
|
from mayan.apps.documents.tests.literals import TEST_SMALL_DOCUMENT_PATH
|
||||||
|
|
||||||
from ..exceptions import (
|
from ..exceptions import (
|
||||||
DocumentAlreadyCheckedOut, DocumentNotCheckedOut,
|
DocumentAlreadyCheckedOut, NewDocumentVersionNotAllowed
|
||||||
NewDocumentVersionNotAllowed
|
|
||||||
)
|
)
|
||||||
from ..models import DocumentCheckout, NewVersionBlock
|
from ..models import DocumentCheckout, NewVersionBlock
|
||||||
|
|
||||||
@@ -49,10 +48,6 @@ class DocumentCheckoutTestCase(DocumentCheckoutTestMixin, GenericDocumentTestCas
|
|||||||
block_new_version=True
|
block_new_version=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_checkin_without_checkout(self):
|
|
||||||
with self.assertRaises(DocumentNotCheckedOut):
|
|
||||||
self.test_document.check_in()
|
|
||||||
|
|
||||||
def test_auto_check_in(self):
|
def test_auto_check_in(self):
|
||||||
self._check_out_test_document()
|
self._check_out_test_document()
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from mayan.apps.common.literals import TIME_DELTA_UNIT_DAYS
|
|
||||||
from mayan.apps.documents.permissions import permission_document_view
|
from mayan.apps.documents.permissions import permission_document_view
|
||||||
from mayan.apps.documents.tests import GenericDocumentViewTestCase
|
from mayan.apps.documents.tests import GenericDocumentViewTestCase
|
||||||
from mayan.apps.sources.links import link_document_version_upload
|
from mayan.apps.sources.links import link_document_version_upload
|
||||||
@@ -23,8 +22,8 @@ class DocumentCheckoutViewTestCase(
|
|||||||
self._check_out_test_document()
|
self._check_out_test_document()
|
||||||
|
|
||||||
response = self._request_test_document_check_in_get_view()
|
response = self._request_test_document_check_in_get_view()
|
||||||
self.assertContains(
|
self.assertNotContains(
|
||||||
response=response, text=self.test_document.label, status_code=200
|
response=response, text=self.test_document.label, status_code=404
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(self.test_document.is_checked_out())
|
self.assertTrue(self.test_document.is_checked_out())
|
||||||
@@ -68,6 +67,86 @@ class DocumentCheckoutViewTestCase(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_document_multiple_check_in_post_view_no_permission(self):
|
||||||
|
# Upload second document
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
self._check_out_test_document(document=self.test_documents[0])
|
||||||
|
self._check_out_test_document(document=self.test_documents[1])
|
||||||
|
|
||||||
|
response = self._request_test_document_multiple_check_in_post_view()
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
self.assertTrue(self.test_documents[0].is_checked_out())
|
||||||
|
self.assertTrue(self.test_documents[1].is_checked_out())
|
||||||
|
self.assertTrue(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[1]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_multiple_check_in_post_view_with_document_0_access(self):
|
||||||
|
# Upload second document
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
self._check_out_test_document(document=self.test_documents[0])
|
||||||
|
self._check_out_test_document(document=self.test_documents[1])
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[0], permission=permission_document_check_in
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_multiple_check_in_post_view()
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
self.assertFalse(self.test_documents[0].is_checked_out())
|
||||||
|
self.assertTrue(self.test_documents[1].is_checked_out())
|
||||||
|
self.assertFalse(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[1]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_multiple_check_in_post_view_with_access(self):
|
||||||
|
# Upload second document
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
self._check_out_test_document(document=self.test_documents[0])
|
||||||
|
self._check_out_test_document(document=self.test_documents[1])
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[0], permission=permission_document_check_in
|
||||||
|
)
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[1], permission=permission_document_check_in
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_multiple_check_in_post_view()
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
self.assertFalse(self.test_documents[0].is_checked_out())
|
||||||
|
self.assertFalse(self.test_documents[1].is_checked_out())
|
||||||
|
self.assertFalse(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[1]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def test_document_check_out_view_no_permission(self):
|
def test_document_check_out_view_no_permission(self):
|
||||||
response = self._request_test_document_check_out_view()
|
response = self._request_test_document_check_out_view()
|
||||||
self.assertEqual(response.status_code, 404)
|
self.assertEqual(response.status_code, 404)
|
||||||
@@ -88,6 +167,102 @@ class DocumentCheckoutViewTestCase(
|
|||||||
|
|
||||||
self.assertTrue(self.test_document.is_checked_out())
|
self.assertTrue(self.test_document.is_checked_out())
|
||||||
|
|
||||||
|
def test_document_multiple_check_out_post_view_no_permission(self):
|
||||||
|
# Upload second document
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[0],
|
||||||
|
permission=permission_document_check_out_detail_view
|
||||||
|
)
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[1],
|
||||||
|
permission=permission_document_check_out_detail_view
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_multiple_check_out_post_view()
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
self.assertFalse(self.test_documents[0].is_checked_out())
|
||||||
|
self.assertFalse(self.test_documents[1].is_checked_out())
|
||||||
|
self.assertFalse(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[1]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_multiple_check_out_post_view_with_document_0_access(self):
|
||||||
|
# Upload second document
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[0], permission=permission_document_check_out
|
||||||
|
)
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[0],
|
||||||
|
permission=permission_document_check_out_detail_view
|
||||||
|
)
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[1],
|
||||||
|
permission=permission_document_check_out_detail_view
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_multiple_check_out_post_view()
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
self.assertTrue(self.test_documents[0].is_checked_out())
|
||||||
|
self.assertFalse(self.test_documents[1].is_checked_out())
|
||||||
|
self.assertTrue(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[1]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_document_multiple_check_out_post_view_with_access(self):
|
||||||
|
# Upload second document
|
||||||
|
self.upload_document()
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[0], permission=permission_document_check_out
|
||||||
|
)
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[1], permission=permission_document_check_out
|
||||||
|
)
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[0],
|
||||||
|
permission=permission_document_check_out_detail_view
|
||||||
|
)
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_documents[1],
|
||||||
|
permission=permission_document_check_out_detail_view
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self._request_test_document_multiple_check_out_post_view()
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
|
||||||
|
self.assertTrue(self.test_documents[0].is_checked_out())
|
||||||
|
self.assertTrue(self.test_documents[1].is_checked_out())
|
||||||
|
self.assertTrue(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[0]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
DocumentCheckout.objects.is_checked_out(
|
||||||
|
document=self.test_documents[1]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def test_document_check_out_detail_view_no_permission(self):
|
def test_document_check_out_detail_view_no_permission(self):
|
||||||
self._check_out_test_document()
|
self._check_out_test_document()
|
||||||
|
|
||||||
@@ -177,45 +352,39 @@ class DocumentCheckoutViewTestCase(
|
|||||||
|
|
||||||
self.assertEqual(resolved_link, None)
|
self.assertEqual(resolved_link, None)
|
||||||
|
|
||||||
def test_document_forcefull_check_in_view_no_permission(self):
|
def test_document_check_in_forcefull_view_no_permission(self):
|
||||||
# Gitlab issue #237
|
# Gitlab issue #237
|
||||||
# Forcefully checking in a document by a user without adequate
|
# Forcefully checking in a document by a user without adequate
|
||||||
# permissions throws out an error
|
# permissions throws out an error
|
||||||
|
|
||||||
self._create_test_case_superuser()
|
self._create_test_user()
|
||||||
self._check_out_test_document(user=self._test_case_superuser)
|
self._check_out_test_document(user=self.test_user)
|
||||||
|
|
||||||
self.grant_access(
|
self.grant_access(
|
||||||
obj=self.test_document, permission=permission_document_check_in
|
obj=self.test_document, permission=permission_document_check_in
|
||||||
)
|
)
|
||||||
|
|
||||||
response = self.post(
|
|
||||||
viewname='checkouts:check_in_document', kwargs={
|
|
||||||
'pk': self.test_document.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
self.assertContains(
|
|
||||||
response=response, text='Insufficient permissions', status_code=403
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertTrue(self.test_document.is_checked_out())
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
self.grant_access(
|
|
||||||
obj=self.test_document, permission=permission_document_check_in
|
|
||||||
)
|
|
||||||
self.grant_access(
|
|
||||||
obj=self.test_document, permission=permission_document_check_in_override
|
|
||||||
)
|
|
||||||
|
|
||||||
response = self.post(
|
response = self.post(
|
||||||
viewname='checkouts:check_in_document', kwargs={
|
viewname='checkouts:check_in_document', kwargs={
|
||||||
'pk': self.test_document.pk
|
'pk': self.test_document.pk
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, 302)
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertTrue(self.test_document.is_checked_out())
|
||||||
|
|
||||||
|
def test_document_check_in_forcefull_view_with_access(self):
|
||||||
|
self._create_test_user()
|
||||||
|
self._check_out_test_document(user=self.test_user)
|
||||||
|
|
||||||
|
self.grant_access(
|
||||||
|
obj=self.test_document,
|
||||||
|
permission=permission_document_check_in_override
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.post(
|
||||||
|
viewname='checkouts:check_in_document', kwargs={
|
||||||
|
'pk': self.test_document.pk
|
||||||
|
}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
self.assertFalse(self.test_document.is_checked_out())
|
self.assertFalse(self.test_document.is_checked_out())
|
||||||
|
|||||||
+21
-139
@@ -1,21 +1,16 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
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.urls import reverse
|
||||||
from django.utils.translation import ugettext_lazy as _, ungettext
|
from django.utils.translation import ugettext_lazy as _, ungettext
|
||||||
|
|
||||||
from mayan.apps.acls.models import AccessControlList
|
from mayan.apps.acls.models import AccessControlList
|
||||||
from mayan.apps.common.generics import (
|
from mayan.apps.common.generics import (
|
||||||
ConfirmView, MultipleObjectConfirmActionView, MultipleObjectFormActionView,
|
MultipleObjectConfirmActionView, MultipleObjectFormActionView,
|
||||||
SingleObjectCreateView, SingleObjectDetailView
|
SingleObjectDetailView
|
||||||
)
|
)
|
||||||
from mayan.apps.common.utils import encapsulate
|
|
||||||
from mayan.apps.documents.models import Document
|
from mayan.apps.documents.models import Document
|
||||||
from mayan.apps.documents.views import DocumentListView
|
from mayan.apps.documents.views import DocumentListView
|
||||||
|
|
||||||
from .exceptions import DocumentAlreadyCheckedOut, DocumentNotCheckedOut
|
|
||||||
from .forms import DocumentCheckoutForm, DocumentCheckoutDefailForm
|
from .forms import DocumentCheckoutForm, DocumentCheckoutDefailForm
|
||||||
from .icons import icon_check_out_info
|
from .icons import icon_check_out_info
|
||||||
from .models import DocumentCheckout
|
from .models import DocumentCheckout
|
||||||
@@ -25,69 +20,9 @@ from .permissions import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
class DocumentCheckinView(ConfirmView):
|
|
||||||
def get_extra_context(self):
|
|
||||||
document = self.get_object()
|
|
||||||
|
|
||||||
context = {
|
|
||||||
'object': document,
|
|
||||||
}
|
|
||||||
|
|
||||||
if document.get_check_out_info().user != self.request.user:
|
|
||||||
context['title'] = _(
|
|
||||||
'You didn\'t originally checked out this document. '
|
|
||||||
'Forcefully check in the document: %s?'
|
|
||||||
) % document
|
|
||||||
else:
|
|
||||||
context['title'] = _('Check in the document: %s?') % document
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
def get_object(self):
|
|
||||||
return get_object_or_404(klass=Document, pk=self.kwargs['pk'])
|
|
||||||
|
|
||||||
def get_post_action_redirect(self):
|
|
||||||
return reverse(
|
|
||||||
viewname='checkouts:check_out_info', kwargs={
|
|
||||||
'pk': self.get_object().pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def view_action(self):
|
|
||||||
document = self.get_object()
|
|
||||||
|
|
||||||
if document.get_check_out_info().user == self.request.user:
|
|
||||||
AccessControlList.objects.check_access(
|
|
||||||
obj=document, permissions=(permission_document_check_in,),
|
|
||||||
user=self.request.user
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
AccessControlList.objects.check_access(
|
|
||||||
obj=document,
|
|
||||||
permissions=(permission_document_check_in_override,),
|
|
||||||
user=self.request.user
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
document.check_in(user=self.request.user)
|
|
||||||
except DocumentNotCheckedOut:
|
|
||||||
messages.error(
|
|
||||||
message=_('Document has not been checked out.'),
|
|
||||||
request=self.request
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
messages.success(
|
|
||||||
message=_(
|
|
||||||
'Document "%s" checked in successfully.'
|
|
||||||
) % document, request=self.request
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
|
|
||||||
class DocumentCheckinView(MultipleObjectConfirmActionView):
|
class DocumentCheckinView(MultipleObjectConfirmActionView):
|
||||||
error_message = 'Unable to check in document "%(instance)s". %(exception)s'
|
error_message = 'Unable to check in document "%(instance)s". %(exception)s'
|
||||||
model = Document
|
model = Document
|
||||||
object_permission = permission_document_check_in
|
|
||||||
pk_url_kwarg = 'pk'
|
pk_url_kwarg = 'pk'
|
||||||
success_message_singular = '%(count)d document checked in.'
|
success_message_singular = '%(count)d document checked in.'
|
||||||
success_message_plural = '%(count)d documents checked in.'
|
success_message_plural = '%(count)d documents checked in.'
|
||||||
@@ -126,63 +61,30 @@ class DocumentCheckinView(MultipleObjectConfirmActionView):
|
|||||||
else:
|
else:
|
||||||
super(DocumentCheckinView, self).get_post_action_redirect()
|
super(DocumentCheckinView, self).get_post_action_redirect()
|
||||||
|
|
||||||
|
def get_source_queryset(self):
|
||||||
|
# object_permission is None to disable restricting queryset mixin
|
||||||
|
# and restrict the queryset ourselves from two permissions
|
||||||
|
|
||||||
|
source_queryset = super(DocumentCheckinView, self).get_source_queryset()
|
||||||
|
|
||||||
|
check_in_queryset = AccessControlList.objects.restrict_queryset(
|
||||||
|
permission=permission_document_check_in, queryset=source_queryset,
|
||||||
|
user=self.request.user
|
||||||
|
)
|
||||||
|
|
||||||
|
check_in_override_queryset = AccessControlList.objects.restrict_queryset(
|
||||||
|
permission=permission_document_check_in_override,
|
||||||
|
queryset=source_queryset, user=self.request.user
|
||||||
|
)
|
||||||
|
|
||||||
|
return check_in_queryset | check_in_override_queryset
|
||||||
|
|
||||||
def object_action(self, form, instance):
|
def object_action(self, form, instance):
|
||||||
DocumentCheckout.objects.check_in_document(
|
DocumentCheckout.business_logic.check_in_document(
|
||||||
document=instance, user=self.request.user
|
document=instance, user=self.request.user
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
class CheckoutDocumentView(SingleObjectCreateView):
|
|
||||||
form_class = DocumentCheckoutForm
|
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
|
||||||
self.document = get_object_or_404(klass=Document, pk=self.kwargs['pk'])
|
|
||||||
|
|
||||||
AccessControlList.objects.check_access(
|
|
||||||
obj=self.document, permissions=(permission_document_check_out,),
|
|
||||||
user=request.user
|
|
||||||
)
|
|
||||||
|
|
||||||
return super(
|
|
||||||
CheckoutDocumentView, self
|
|
||||||
).dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
try:
|
|
||||||
instance = form.save(commit=False)
|
|
||||||
instance.user = self.request.user
|
|
||||||
instance.document = self.document
|
|
||||||
instance.save()
|
|
||||||
except DocumentAlreadyCheckedOut:
|
|
||||||
messages.error(
|
|
||||||
message=_('Document already checked out.'),
|
|
||||||
request=self.request
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
messages.success(
|
|
||||||
message=_(
|
|
||||||
'Document "%s" checked out successfully.'
|
|
||||||
) % self.document, request=self.request
|
|
||||||
)
|
|
||||||
|
|
||||||
return HttpResponseRedirect(redirect_to=self.get_success_url())
|
|
||||||
|
|
||||||
def get_extra_context(self):
|
|
||||||
return {
|
|
||||||
'object': self.document,
|
|
||||||
'title': _('Check out document: %s') % self.document
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_post_action_redirect(self):
|
|
||||||
return reverse(
|
|
||||||
viewname='checkouts:check_out_info', kwargs={
|
|
||||||
'pk': self.document.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
class DocumentCheckoutView(MultipleObjectFormActionView):
|
class DocumentCheckoutView(MultipleObjectFormActionView):
|
||||||
error_message = 'Unable to checkout document "%(instance)s". %(exception)s'
|
error_message = 'Unable to checkout document "%(instance)s". %(exception)s'
|
||||||
form_class = DocumentCheckoutForm
|
form_class = DocumentCheckoutForm
|
||||||
@@ -261,26 +163,6 @@ class DocumentCheckoutListView(DocumentListView):
|
|||||||
context = super(DocumentCheckoutListView, self).get_extra_context()
|
context = super(DocumentCheckoutListView, self).get_extra_context()
|
||||||
context.update(
|
context.update(
|
||||||
{
|
{
|
||||||
'extra_columns': (
|
|
||||||
{
|
|
||||||
'name': _('User'),
|
|
||||||
'attribute': encapsulate(
|
|
||||||
lambda document: document.get_check_out_info().user.get_full_name() or document.get_check_out_info().user
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': _('Checkout time and date'),
|
|
||||||
'attribute': encapsulate(
|
|
||||||
lambda document: document.get_check_out_info().checkout_datetime
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'name': _('Checkout expiration'),
|
|
||||||
'attribute': encapsulate(
|
|
||||||
lambda document: document.get_check_out_info().expiration_datetime
|
|
||||||
)
|
|
||||||
},
|
|
||||||
),
|
|
||||||
'no_results_icon': icon_check_out_info,
|
'no_results_icon': icon_check_out_info,
|
||||||
'no_results_text': _(
|
'no_results_text': _(
|
||||||
'Checking out a document, blocks certain operations '
|
'Checking out a document, blocks certain operations '
|
||||||
|
|||||||
Reference in New Issue
Block a user