Refactor checkouts app

Change "checkin" usage to "check_in".

Update URL parameters to the "_id" form.

Add support to checkout and check in multiple documents.

Optimize queries that used an ID list of documents for
filtering using values_list('pk', flat=True). These
queries now use .values('pk') as a subquery.

Add pre save hooks to block new document version uploads.

Signed-off-by: Roberto Rosario <Roberto.Rosario@mayan-edms.com>
This commit is contained in:
Roberto Rosario
2019-01-31 05:53:09 -04:00
parent 3976766abe
commit e007af6b3f
14 changed files with 333 additions and 351 deletions

View File

@@ -225,6 +225,8 @@
- Remove the permissions to grant or revoke a permission to a role.
The instead the role edit permission is used.
- Add a test mixin to generate random model primary keys.
- Add support for checkout and check in multiple documents at
the same time.
3.1.9 (2018-11-01)
==================

View File

@@ -7,7 +7,7 @@ from mayan.apps.documents.permissions import permission_document_view
from .models import DocumentCheckout
from .permissions import (
permission_document_checkin, permission_document_checkin_override,
permission_document_check_in, permission_document_check_in_override,
permission_document_checkout_detail_view
)
from .serializers import (
@@ -78,12 +78,12 @@ class APICheckedoutDocumentView(generics.RetrieveDestroyAPIView):
if document.checkout_info().user == request.user:
AccessControlList.objects.check_access(
permissions=permission_document_checkin, user=request.user,
permissions=permission_document_check_in, user=request.user,
obj=document
)
else:
AccessControlList.objects.check_access(
permissions=permission_document_checkin_override,
permissions=permission_document_check_in_override,
user=request.user, obj=document
)

View File

@@ -10,7 +10,7 @@ from django.utils.translation import ugettext_lazy as _
from mayan.apps.acls import ModelPermission
from mayan.apps.common import (
MayanAppConfig, menu_facet, menu_main, menu_sidebar
MayanAppConfig, menu_facet, menu_main, menu_multi_item, menu_sidebar
)
from mayan.apps.dashboards.dashboards import dashboard_main
from mayan.apps.events import ModelEventType
@@ -23,8 +23,9 @@ from .events import (
)
from .handlers import handler_check_new_version_creation
from .links import (
link_checkin_document, link_checkout_document, link_checkout_info,
link_checkout_list
link_document_check_in, link_document_checkout, link_document_checkout_info,
link_document_checkout_list, link_document_multiple_check_in,
link_document_multiple_checkout
)
from .literals import CHECK_EXPIRED_CHECK_OUTS_INTERVAL
from .methods import (
@@ -32,7 +33,7 @@ from .methods import (
method_is_checked_out
)
from .permissions import (
permission_document_checkin, permission_document_checkin_override,
permission_document_check_in, permission_document_check_in_override,
permission_document_checkout, permission_document_checkout_detail_view
)
from .queues import * # NOQA
@@ -79,8 +80,8 @@ class CheckoutsApp(MayanAppConfig):
ModelPermission.register(
model=Document, permissions=(
permission_document_checkout,
permission_document_checkin,
permission_document_checkin_override,
permission_document_check_in,
permission_document_check_in_override,
permission_document_checkout_detail_view
)
)
@@ -115,13 +116,18 @@ class CheckoutsApp(MayanAppConfig):
widget=DashboardWidgetTotalCheckouts, order=-1
)
menu_facet.bind_links(links=(link_checkout_info,), sources=(Document,))
menu_main.bind_links(links=(link_checkout_list,), position=98)
menu_facet.bind_links(links=(link_document_checkout_info,), sources=(Document,))
menu_main.bind_links(links=(link_document_checkout_list,), position=98)
menu_multi_item.bind_links(
links=(
link_document_multiple_check_in, link_document_multiple_checkout
), sources=(Document,)
)
menu_sidebar.bind_links(
links=(link_checkout_document, link_checkin_document),
links=(link_document_checkout, link_document_check_in),
sources=(
'checkouts:checkout_info', 'checkouts:checkout_document',
'checkouts:checkin_document'
'checkouts:document_checkout_info', 'checkouts:document_checkout',
'checkouts:document_check_in'
)
)

View File

@@ -14,7 +14,7 @@ from .permissions import permission_document_checkout_detail_view
class DashboardWidgetTotalCheckouts(DashboardWidgetNumeric):
icon_class = icon_dashboard_checkouts
label = _('Checkedout documents')
link = reverse_lazy(viewname='checkouts:checkout_list')
link = reverse_lazy(viewname='checkouts:document_checkout_list')
def render(self, request):
AccessControlList = apps.get_model(

View File

@@ -0,0 +1,13 @@
from __future__ import unicode_literals
from django.apps import apps
def hook_is_new_version_allowed(document_version):
NewVersionBlock = apps.get_model(
app_label='checkouts', model_name='NewVersionBlock'
)
NewVersionBlock.objects.new_versions_allowed(
document_version=document_version.document
)

View File

@@ -8,8 +8,8 @@ from .icons import (
icon_checkin_document, icon_checkout_document, icon_checkout_info
)
from .permissions import (
permission_document_checkin, permission_document_checkin_override,
permission_document_checkout, permission_document_checkout_detail_view
permission_document_check_in, permission_document_checkout,
permission_document_checkout_detail_view
)
@@ -29,23 +29,32 @@ def is_not_checked_out(context):
return True
link_checkout_list = Link(
link_document_checkout_list = Link(
icon_class=icon_checkout_info, text=_('Checkouts'),
view='checkouts:checkout_list'
view='checkouts:document_checkout_list'
)
link_checkout_document = Link(
args='object.pk', condition=is_not_checked_out,
icon_class=icon_checkout_document,
link_document_checkout = Link(
condition=is_not_checked_out, icon_class=icon_checkout_document,
kwargs={'document_id': 'object.pk'},
permission=permission_document_checkout, text=_('Check out document'),
view='checkouts:checkout_document',
view='checkouts:document_checkout',
)
link_checkin_document = Link(
args='object.pk', condition=is_checked_out,
icon_class=icon_checkin_document, permission=permission_document_checkin,
text=_('Check in document'), view='checkouts:checkin_document',
link_document_multiple_checkout = Link(
icon_class=icon_checkout_document,
permission=permission_document_checkout, text=_('Check out'),
view='checkouts:document_multiple_checkout',
)
link_checkout_info = Link(
args='resolved_object.pk', icon_class=icon_checkout_info,
link_document_check_in = Link(
condition=is_checked_out, icon_class=icon_checkin_document,
kwargs={'document_id': 'object.pk'}, permission=permission_document_check_in,
text=_('Check in document'), view='checkouts:document_check_in',
)
link_document_multiple_check_in = Link(
icon_class=icon_checkin_document, permission=permission_document_check_in,
text=_('Check in'), view='checkouts:document_multiple_check_in',
)
link_document_checkout_info = Link(
icon_class=icon_checkout_info, kwargs={'document_id': 'resolved_object.pk'},
permission=permission_document_checkout_detail_view,
text=_('Check in/out'), view='checkouts:checkout_info',
text=_('Check in/out'), view='checkouts:document_checkout_info',
)

View File

@@ -2,48 +2,54 @@ from __future__ import absolute_import, unicode_literals
import logging
from django.apps import apps
from django.db import models
from django.core.exceptions import PermissionDenied
from django.db import models, transaction
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from mayan.apps.acls.models import AccessControlList
from mayan.apps.documents.models import Document
from .events import (
event_document_auto_check_in, event_document_check_in,
event_document_forceful_check_in
)
from .exceptions import DocumentNotCheckedOut
from .exceptions import DocumentNotCheckedOut, NewDocumentVersionNotAllowed
from .literals import STATE_CHECKED_IN, STATE_CHECKED_OUT
from .permissions import permission_document_check_in_override
logger = logging.getLogger(__name__)
class DocumentCheckoutManager(models.Manager):
def are_document_new_versions_allowed(self, document, user=None):
try:
checkout_info = self.document_checkout_info(document=document)
except DocumentNotCheckedOut:
return True
else:
return not checkout_info.block_new_version
def check_in_document(self, document, user=None):
try:
document_checkout = self.model.objects.get(document=document)
except self.model.DoesNotExist:
raise DocumentNotCheckedOut
raise DocumentNotCheckedOut(
_('Document not checked out.')
)
else:
if user:
if self.get_document_checkout_info(document=document).user != user:
event_document_forceful_check_in.commit(
actor=user, target=document
)
with transaction.atomic():
if user:
if self.get_document_checkout_info(document=document).user != user:
try:
AccessControlList.objects.check_access(
obj=document, permission=permission_document_check_in_override,
user=user
)
except PermissionDenied:
return
else:
event_document_forceful_check_in.commit(
actor=user, target=document
)
else:
event_document_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)
event_document_auto_check_in.commit(target=document)
document_checkout.delete()
document_checkout.delete()
def check_in_expired_check_outs(self):
for document in self.get_expired_check_outs():
@@ -57,15 +63,10 @@ class DocumentCheckoutManager(models.Manager):
def checked_out_documents(self):
return Document.objects.filter(
pk__in=self.model.objects.all().values_list(
'document__pk', flat=True
)
pk__in=self.model.objects.values('document__id')
)
def get_by_natural_key(self, document_natural_key):
Document = apps.get_model(
app_label='documents', model_name='Document'
)
try:
document = Document.objects.get_by_natural_key(document_natural_key)
except Document.DoesNotExist:
@@ -87,15 +88,15 @@ class DocumentCheckoutManager(models.Manager):
def get_expired_check_outs(self):
expired_list = Document.objects.filter(
pk__in=self.model.objects.filter(
pk__in=self.filter(
expiration_datetime__lte=now()
).values_list('document__pk', flat=True)
).values('document__id')
)
logger.debug('expired_list: %s', expired_list)
return expired_list
def is_document_checked_out(self, document):
if self.model.objects.filter(document=document):
if self.filter(document=document).exists():
return True
else:
return False
@@ -105,13 +106,7 @@ class NewVersionBlockManager(models.Manager):
def block(self, document):
self.get_or_create(document=document)
def is_blocked(self, document):
return self.filter(document=document).exists()
def get_by_natural_key(self, document_natural_key):
Document = apps.get_model(
app_label='documents', model_name='Document'
)
try:
document = Document.objects.get_by_natural_key(document_natural_key)
except Document.DoesNotExist:
@@ -119,5 +114,12 @@ class NewVersionBlockManager(models.Manager):
return self.get(document__pk=document.pk)
def is_blocked(self, document):
return self.filter(document=document).exists()
def new_versions_allowed(self, document):
if self.filter(document=document).exist():
raise NewDocumentVersionNotAllowed
def unblock(self, document):
self.filter(document=document).delete()

View File

@@ -4,7 +4,7 @@ import logging
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.db import models, transaction
from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible
from django.utils.timezone import now
@@ -68,13 +68,14 @@ class DocumentCheckout(models.Model):
)
def delete(self, *args, **kwargs):
# TODO: enclose in transaction
NewVersionBlock.objects.unblock(self.document)
super(DocumentCheckout, self).delete(*args, **kwargs)
with transaction.atomic():
NewVersionBlock.objects.unblock(document=self.document)
super(DocumentCheckout, self).delete(*args, **kwargs)
def get_absolute_url(self):
return reverse(
viewname='checkout:checkout_info', kwargs={'pk': self.document.pk}
viewname='checkout:checkout_info',
kwargs={'document_id': self.document.pk}
)
def natural_key(self):
@@ -82,25 +83,27 @@ class DocumentCheckout(models.Model):
natural_key.dependencies = ['documents.Document']
def save(self, *args, **kwargs):
# TODO: enclose in transaction
new_checkout = not self.pk
if not new_checkout or self.document.is_checked_out():
raise DocumentAlreadyCheckedOut
result = super(DocumentCheckout, self).save(*args, **kwargs)
if new_checkout:
event_document_check_out.commit(
actor=self.user, target=self.document
)
if self.block_new_version:
NewVersionBlock.objects.block(self.document)
logger.info(
'Document "%s" checked out by user "%s"',
self.document, self.user
raise DocumentAlreadyCheckedOut(
_('Document already checked out.')
)
return result
with transaction.atomic():
result = super(DocumentCheckout, self).save(*args, **kwargs)
if new_checkout:
event_document_check_out.commit(
actor=self.user, target=self.document
)
if self.block_new_version:
NewVersionBlock.objects.block(self.document)
logger.info(
'Document "%s" checked out by user "%s"',
self.document, self.user
)
return result
class NewVersionBlock(models.Model):

View File

@@ -6,10 +6,10 @@ from mayan.apps.permissions import PermissionNamespace
namespace = PermissionNamespace(label=_('Document checkout'), name='checkouts')
permission_document_checkin = namespace.add_permission(
permission_document_check_in = namespace.add_permission(
label=_('Check in documents'), name='checkin_document'
)
permission_document_checkin_override = namespace.add_permission(
permission_document_check_in_override = namespace.add_permission(
label=_('Forcefully check in documents'), name='checkin_document_override'
)
permission_document_checkout = namespace.add_permission(

View File

@@ -42,8 +42,8 @@ class NewDocumentCheckoutSerializer(serializers.ModelSerializer):
document = Document.objects.get(pk=validated_data.pop('document_pk'))
AccessControlList.objects.check_access(
permissions=permission_document_checkout,
obj=document, user=self.context['request'].user
obj=document, permissions=permission_document_checkout,
user=self.context['request'].user
)
validated_data['document'] = document

View File

@@ -23,7 +23,7 @@ class DocumentCheckoutTestCase(DocumentTestMixin, BaseTestCase):
DocumentCheckout.objects.checkout_document(
document=self.document, expiration_datetime=expiration_datetime,
user=self.admin_user, block_new_version=True
user=self._test_case_user, block_new_version=True
)
self.assertTrue(self.document.is_checked_out())
@@ -33,29 +33,12 @@ class DocumentCheckoutTestCase(DocumentTestMixin, BaseTestCase):
)
)
def test_version_creation_blocking(self):
expiration_datetime = now() + datetime.timedelta(days=1)
# Silence unrelated logging
logging.getLogger('mayan.apps.documents.models').setLevel(
level=logging.CRITICAL
)
DocumentCheckout.objects.checkout_document(
document=self.document, expiration_datetime=expiration_datetime,
user=self.admin_user, block_new_version=True
)
with self.assertRaises(NewDocumentVersionNotAllowed):
with open(TEST_SMALL_DOCUMENT_PATH, mode='rb') as file_object:
self.document.new_version(file_object=file_object)
def test_checkin_in(self):
expiration_datetime = now() + datetime.timedelta(days=1)
DocumentCheckout.objects.checkout_document(
document=self.document, expiration_datetime=expiration_datetime,
user=self.admin_user, block_new_version=True
user=self._test_case_user, block_new_version=True
)
self.document.check_in()
@@ -72,13 +55,13 @@ class DocumentCheckoutTestCase(DocumentTestMixin, BaseTestCase):
DocumentCheckout.objects.checkout_document(
document=self.document, expiration_datetime=expiration_datetime,
user=self.admin_user, block_new_version=True
user=self._test_case_user, block_new_version=True
)
with self.assertRaises(DocumentAlreadyCheckedOut):
DocumentCheckout.objects.checkout_document(
document=self.document,
expiration_datetime=expiration_datetime, user=self.admin_user,
expiration_datetime=expiration_datetime, user=self._test_case_user,
block_new_version=True
)
@@ -91,7 +74,7 @@ class DocumentCheckoutTestCase(DocumentTestMixin, BaseTestCase):
DocumentCheckout.objects.checkout_document(
document=self.document, expiration_datetime=expiration_datetime,
user=self.admin_user, block_new_version=True
user=self._test_case_user, block_new_version=True
)
time.sleep(.11)
@@ -100,18 +83,6 @@ class DocumentCheckoutTestCase(DocumentTestMixin, BaseTestCase):
self.assertFalse(self.document.is_checked_out())
def test_blocking_new_versions(self):
# Silence unrelated logging
logging.getLogger('mayan.apps.documents.models').setLevel(
level=logging.CRITICAL
)
NewVersionBlock.objects.block(document=self.document)
with self.assertRaises(NewDocumentVersionNotAllowed):
with open(TEST_SMALL_DOCUMENT_PATH, mode='rb') as file_object:
self.document.new_version(file_object=file_object)
class NewVersionBlockTestCase(DocumentTestMixin, BaseTestCase):
def test_blocking(self):
@@ -141,3 +112,32 @@ class NewVersionBlockTestCase(DocumentTestMixin, BaseTestCase):
self.assertFalse(
NewVersionBlock.objects.is_blocked(document=self.document)
)
def test_blocking_new_versions(self):
# Silence unrelated logging
logging.getLogger('mayan.apps.documents.models').setLevel(
level=logging.CRITICAL
)
NewVersionBlock.objects.block(document=self.document)
with self.assertRaises(NewDocumentVersionNotAllowed):
with open(TEST_SMALL_DOCUMENT_PATH, mode='rb') as file_object:
self.document.new_version(file_object=file_object)
def test_version_creation_blocking(self):
expiration_datetime = now() + datetime.timedelta(days=1)
# Silence unrelated logging
logging.getLogger('mayan.apps.documents.models').setLevel(
level=logging.CRITICAL
)
DocumentCheckout.objects.checkout_document(
document=self.document, expiration_datetime=expiration_datetime,
user=self._test_case_user, block_new_version=True
)
with self.assertRaises(NewDocumentVersionNotAllowed):
with open(TEST_SMALL_DOCUMENT_PATH, mode='rb') as file_object:
self.document.new_version(file_object=file_object)

View File

@@ -8,62 +8,53 @@ from django.utils.timezone import now
from mayan.apps.common.literals import TIME_DELTA_UNIT_DAYS
from mayan.apps.documents.tests import GenericDocumentViewTestCase
from mayan.apps.sources.links import link_upload_version
from mayan.apps.user_management.tests import (
TEST_ADMIN_PASSWORD, TEST_ADMIN_USERNAME, TEST_USER_PASSWORD,
TEST_USER_USERNAME
)
from ..models import DocumentCheckout
from ..permissions import (
permission_document_checkin, permission_document_checkin_override,
permission_document_check_in, permission_document_check_in_override,
permission_document_checkout, permission_document_checkout_detail_view
)
class DocumentCheckoutViewTestCase(GenericDocumentViewTestCase):
create_test_case_superuser = True
def _checkout_document(self):
expiration_datetime = now() + datetime.timedelta(days=1)
DocumentCheckout.objects.checkout_document(
document=self.document, expiration_datetime=expiration_datetime,
user=self._test_case_user, block_new_version=True
)
self.assertTrue(self.document.is_checked_out())
def _request_document_check_in_view(self):
return self.post(
viewname='checkouts:checkin_document',
kwargs={'document_pk': self.document.pk}
viewname='checkouts:document_check_in',
kwargs={'document_id': self.document.pk}
)
def test_checkin_document_view_no_permission(self):
self.login_user()
expiration_datetime = now() + datetime.timedelta(days=1)
DocumentCheckout.objects.checkout_document(
document=self.document, expiration_datetime=expiration_datetime,
user=self.user, block_new_version=True
)
self.assertTrue(self.document.is_checked_out())
def test_document_check_in_view_no_permission(self):
self._checkout_document()
response = self._request_document_check_in_view()
self.assertEquals(response.status_code, 403)
self.assertEquals(response.status_code, 404)
self.assertTrue(self.document.is_checked_out())
def test_checkin_document_view_with_access(self):
self.login_user()
expiration_datetime = now() + datetime.timedelta(days=1)
DocumentCheckout.objects.checkout_document(
document=self.document, expiration_datetime=expiration_datetime,
user=self.user, block_new_version=True
)
self.assertTrue(self.document.is_checked_out())
def test_document_check_in_view_with_access(self):
self._checkout_document()
self.grant_access(
obj=self.document, permission=permission_document_checkin
obj=self.document, permission=permission_document_check_in
)
self.grant_access(
obj=self.document,
permission=permission_document_checkout_detail_view
)
response = self._request_document_check_in_view()
self.assertEquals(response.status_code, 302)
self.assertFalse(self.document.is_checked_out())
self.assertFalse(
DocumentCheckout.objects.is_document_checked_out(
@@ -73,8 +64,8 @@ class DocumentCheckoutViewTestCase(GenericDocumentViewTestCase):
def _request_document_checkout_view(self):
return self.post(
viewname='checkouts:checkout_document',
kwargs={'document_pk': self.document.pk},
viewname='checkouts:document_checkout',
kwargs={'document_id': self.document.pk},
data={
'expiration_datetime_0': 2,
'expiration_datetime_1': TIME_DELTA_UNIT_DAYS,
@@ -83,14 +74,11 @@ class DocumentCheckoutViewTestCase(GenericDocumentViewTestCase):
)
def test_checkout_document_view_no_permission(self):
self.login_user()
response = self._request_document_checkout_view()
self.assertEquals(response.status_code, 403)
self.assertEquals(response.status_code, 404)
self.assertFalse(self.document.is_checked_out())
def test_checkout_document_view_with_access(self):
self.login_user()
self.grant_access(
obj=self.document, permission=permission_document_checkout
)
@@ -98,9 +86,9 @@ class DocumentCheckoutViewTestCase(GenericDocumentViewTestCase):
obj=self.document,
permission=permission_document_checkout_detail_view
)
response = self._request_document_checkout_view()
self.assertEquals(response.status_code, 302)
self.assertTrue(self.document.is_checked_out())
def test_document_new_version_after_checkout(self):
@@ -113,25 +101,15 @@ class DocumentCheckoutViewTestCase(GenericDocumentViewTestCase):
- Link to upload version view should not resolve
- Upload version view should reject request
"""
self.login(
username=TEST_ADMIN_USERNAME, password=TEST_ADMIN_PASSWORD
)
self.login_superuser()
expiration_datetime = now() + datetime.timedelta(days=1)
DocumentCheckout.objects.checkout_document(
document=self.document, expiration_datetime=expiration_datetime,
user=self.admin_user, block_new_version=True
)
self.assertTrue(self.document.is_checked_out())
self._checkout_document()
response = self.post(
viewname='sources:upload_version',
kwargs={'document_pk': self.document.pk},
kwargs={'document_id': self.document.pk},
follow=True
)
self.assertContains(
response, text='blocked from uploading',
status_code=200
@@ -139,7 +117,7 @@ class DocumentCheckoutViewTestCase(GenericDocumentViewTestCase):
response = self.get(
viewname='documents:document_version_list',
kwargs={'document_pk': self.document.pk},
kwargs={'document_id': self.document.pk},
follow=True
)
@@ -163,28 +141,22 @@ class DocumentCheckoutViewTestCase(GenericDocumentViewTestCase):
DocumentCheckout.objects.checkout_document(
document=self.document, expiration_datetime=expiration_datetime,
user=self.admin_user, block_new_version=True
user=self._test_case_superuser, block_new_version=True
)
self.assertTrue(self.document.is_checked_out())
self.login(
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
self.grant_access(
obj=self.document, permission=permission_document_check_in
)
self.role.permissions.add(
permission_document_checkin.stored_permission
self.grant_access(
obj=self.document, permission=permission_document_checkout
)
self.role.permissions.add(
permission_document_checkout.stored_permission
)
response = self.post(
viewname='checkouts:checkin_document',
kwargs={'document_pk': self.document.pk},
viewname='checkouts:document_check_in',
kwargs={'document_id': self.document.pk},
follow=True
)
self.assertContains(
response, text='Insufficient permissions', status_code=403
)
@@ -192,34 +164,20 @@ class DocumentCheckoutViewTestCase(GenericDocumentViewTestCase):
self.assertTrue(self.document.is_checked_out())
def test_forcefull_check_in_document_view_with_permission(self):
expiration_datetime = now() + datetime.timedelta(days=1)
self._checkout_document()
DocumentCheckout.objects.checkout_document(
document=self.document, expiration_datetime=expiration_datetime,
user=self.admin_user, block_new_version=True
self.grant_access(
obj=self.document, permission=permission_document_check_in
)
self.assertTrue(self.document.is_checked_out())
self.login(
username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD
self.grant_access(
obj=self.document, permission=permission_document_check_in_override
)
self.role.permissions.add(
permission_document_checkin.stored_permission
)
self.role.permissions.add(
permission_document_checkin.stored_permission
)
self.role.permissions.add(
permission_document_checkin_override.stored_permission
)
self.role.permissions.add(
permission_document_checkout_detail_view.stored_permission
self.grant_access(
obj=self.document, permission=permission_document_checkout_detail_view
)
response = self.post(
viewname='checkouts:checkin_document',
kwargs={'document_pk': self.document.pk},
viewname='checkouts:document_check_in',
kwargs={'document_id': self.document.pk},
follow=True
)

View File

@@ -4,26 +4,34 @@ from django.conf.urls import url
from .api_views import APICheckedoutDocumentListView, APICheckedoutDocumentView
from .views import (
CheckoutDetailView, CheckoutDocumentView, CheckoutListView,
DocumentCheckinView
DocumentCheckinView, DocumentCheckoutView, DocumentCheckoutDetailView,
DocumentCheckoutListView
)
urlpatterns = [
url(
regex=r'^documents/$', name='checkout_list',
view=CheckoutListView.as_view()
regex=r'^documents/$', name='document_checkout_list',
view=DocumentCheckoutListView.as_view()
),
url(
regex=r'^documents/(?P<document_pk>\d+)/check/out/$',
name='checkout_document', view=CheckoutDocumentView.as_view()
regex=r'^documents/(?P<document_id>\d+)/check_in/$',
name='document_check_in', view=DocumentCheckinView.as_view()
),
url(
regex=r'^documents/(?P<document_pk>\d+)/check/in/$',
name='checkin_document', view=DocumentCheckinView.as_view()
regex=r'^documents/multiple/check_in/$',
name='document_multiple_check_in', view=DocumentCheckinView.as_view()
),
url(
regex=r'^documents/(?P<document_pk>\d+)/check/info/$',
name='checkout_info', view=CheckoutDetailView.as_view()
regex=r'^documents/(?P<document_id>\d+)/checkout/$',
name='document_checkout', view=DocumentCheckoutView.as_view()
),
url(
regex=r'^documents/multiple/checkout/$',
name='document_multiple_checkout', view=DocumentCheckoutView.as_view()
),
url(
regex=r'^documents/(?P<document_id>\d+)/checkout/info/$',
name='document_checkout_info', view=DocumentCheckoutDetailView.as_view()
),
]
@@ -33,7 +41,7 @@ api_urls = [
view=APICheckedoutDocumentListView.as_view()
),
url(
regex=r'^checkouts/(?P<document_pk>[0-9]+)/checkout_info/$',
regex=r'^checkouts/(?P<document_id>\d+)/checkout_info/$',
name='checkedout-document-view',
view=APICheckedoutDocumentView.as_view()
),

View File

@@ -1,86 +1,145 @@
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.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
MultipleObjectConfirmActionView, MultipleObjectFormActionView,
SingleObjectDetailView
)
from mayan.apps.common.utils import encapsulate
from mayan.apps.documents.models import Document
from mayan.apps.documents.views import DocumentListView
from .exceptions import DocumentAlreadyCheckedOut, DocumentNotCheckedOut
from .forms import DocumentCheckoutDefailForm, DocumentCheckoutForm
from .icons import icon_checkout_info
from .models import DocumentCheckout
from .permissions import (
permission_document_checkin, permission_document_checkin_override,
permission_document_checkout, permission_document_checkout_detail_view
permission_document_check_in, permission_document_checkout,
permission_document_checkout_detail_view
)
class CheckoutDocumentView(SingleObjectCreateView):
form_class = DocumentCheckoutForm
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 = 'document_id'
success_message = '%(count)d document checked in.'
success_message_plural = '%(count)d documents checked in.'
def dispatch(self, request, *args, **kwargs):
self.document = get_object_or_404(
klass=Document, pk=self.kwargs['document_pk']
)
def get_extra_context(self):
queryset = self.get_object_list()
AccessControlList.objects.check_access(
obj=self.document, permissions=permission_document_checkout,
user=request.user
)
result = {
'title': ungettext(
singular='Check in %(count)d document',
plural='Check in %(count)d documents',
number=queryset.count()
) % {
'count': queryset.count(),
}
}
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(
request=self.request,
message=_('Document already checked out.')
if queryset.count() == 1:
result.update(
{
'object': queryset.first(),
'title': _(
'Check in document: %s'
) % queryset.first()
}
)
except Exception as exception:
messages.error(
request=self.request,
message=_('Error trying to check out document; %s') % exception
return result
def get_post_object_action_url(self):
if self.action_count == 1:
return reverse(
viewname='checkouts:document_checkout_info',
kwargs={'document_id': self.action_id_list[0]}
)
else:
messages.success(
request=self.request,
message=_(
'Document "%s" checked out successfully.'
) % self.document
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 DocumentCheckoutView(MultipleObjectFormActionView):
error_message = 'Unable to checkout document "%(instance)s". %(exception)s'
form_class = DocumentCheckoutForm
model = Document
object_permission = permission_document_checkout
pk_url_kwarg = 'document_id'
success_message = '%(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 HttpResponseRedirect(redirect_to=self.get_success_url())
return result
def get_post_object_action_url(self):
if self.action_count == 1:
return reverse(
viewname='checkouts:document_checkout_info',
kwargs={'document_id': self.action_id_list[0]}
)
else:
super(DocumentCheckoutView, self).get_post_action_redirect()
def object_action(self, form, instance):
DocumentCheckout.objects.checkout_document(
block_new_version=form.cleaned_data['block_new_version'],
document=instance,
expiration_datetime=form.cleaned_data['expiration_datetime'],
user=self.request.user,
)
class DocumentCheckoutDetailView(SingleObjectDetailView):
form_class = DocumentCheckoutDefailForm
model = Document
object_permission = permission_document_checkout_detail_view
def get_extra_context(self):
return {
'object': self.document,
'title': _('Check out document: %s') % self.document
'object': self.get_object(),
'title': _(
'Check out details for document: %s'
) % self.get_object()
}
def get_post_action_redirect(self):
return reverse(
viewname='checkouts:checkout_info',
kwargs={'document_pk': self.document.pk}
)
def get_object(self):
return get_object_or_404(klass=Document, pk=self.kwargs['document_id'])
class CheckoutListView(DocumentListView):
class DocumentCheckoutListView(DocumentListView):
def get_document_queryset(self):
return AccessControlList.objects.restrict_queryset(
permission=permission_document_checkout_detail_view,
@@ -89,7 +148,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': (
@@ -123,81 +182,3 @@ class CheckoutListView(DocumentListView):
}
)
return context
class CheckoutDetailView(SingleObjectDetailView):
form_class = DocumentCheckoutDefailForm
model = Document
object_permission = permission_document_checkout_detail_view
def get_extra_context(self):
return {
'object': self.get_object(),
'title': _(
'Check out details for document: %s'
) % self.get_object()
}
def get_object(self):
return get_object_or_404(klass=Document, pk=self.kwargs['document_pk'])
class DocumentCheckinView(ConfirmView):
def get_extra_context(self):
document = self.get_object()
context = {
'object': document,
}
if document.get_checkout_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['document_pk'])
def get_post_action_redirect(self):
return reverse(
viewname='checkouts:checkout_info',
kwargs={'document_pk': self.get_object().pk}
)
def view_action(self):
document = self.get_object()
if document.get_checkout_info().user == self.request.user:
AccessControlList.objects.check_access(
obj=document, permissions=permission_document_checkin,
user=self.request.user
)
else:
AccessControlList.objects.check_access(
obj=document, permissions=permission_document_checkin_override,
user=self.request.user
)
try:
document.check_in(user=self.request.user)
except DocumentNotCheckedOut:
messages.error(
request=self.request, message=_(
'Document has not been checked out.'
)
)
except Exception as exception:
messages.error(
request=self.request,
message=_('Error trying to check in document; %s') % exception
)
else:
messages.success(
request=self.request,
message=_('Document "%s" checked in successfully.') % document
)