Files
mayan-edms/mayan/apps/cabinets/models.py
Roberto Rosario 7971b081a9 Add transaction handling
Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
2019-05-15 01:35:50 -04:00

162 lines
5.7 KiB
Python

from __future__ import absolute_import, unicode_literals
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
from django.db import connection, models, transaction
from django.urls import reverse
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from mptt.fields import TreeForeignKey
from mptt.models import MPTTModel
from mayan.apps.acls.models import AccessControlList
from mayan.apps.documents.models import Document
from mayan.apps.documents.permissions import permission_document_view
from .events import (
event_cabinet_created, event_cabinet_edited, event_cabinet_add_document,
event_cabinet_remove_document
)
from .search import cabinet_search # NOQA
@python_2_unicode_compatible
class Cabinet(MPTTModel):
"""
Model to store a hierarchical tree of document containers. Each container
can store an unlimited number of documents using an M2M field. Only
the top level container is can have an ACL. All child container's
access is delegated to their corresponding root container.
"""
parent = TreeForeignKey(
blank=True, db_index=True, null=True, on_delete=models.CASCADE,
related_name='children', to='self'
)
label = models.CharField(max_length=128, verbose_name=_('Label'))
documents = models.ManyToManyField(
blank=True, related_name='cabinets', to=Document,
verbose_name=_('Documents')
)
class Meta:
ordering = ('parent__label', 'label')
# unique_together doesn't work if there is a FK
# https://code.djangoproject.com/ticket/1751
unique_together = ('parent', 'label')
verbose_name = _('Cabinet')
verbose_name_plural = _('Cabinets')
def __str__(self):
return self.get_full_path()
def document_add(self, document, _user=None):
"""
Add a document to a container. This can be done without using this
method but this method provides the event commit already coded.
"""
with transaction.atomic():
self.documents.add(document)
event_cabinet_add_document.commit(
action_object=self, actor=_user, target=document
)
def document_remove(self, document, _user=None):
"""
Remove a document from a cabinet. This method provides the
corresponding event commit.
"""
with transaction.atomic():
self.documents.remove(document)
event_cabinet_remove_document.commit(
action_object=self, actor=_user, target=document
)
def get_absolute_url(self):
return reverse(viewname='cabinets:cabinet_view', kwargs={'pk': self.pk})
def get_document_count(self, user):
"""
Return numeric count of the total documents in a cabinet. The count
is filtered by access.
"""
return self.get_documents_queryset(user=user).count()
def get_documents_queryset(self, user):
"""
Provide a queryset of the documents in a cabinet. The queryset is
filtered by access.
"""
return AccessControlList.objects.restrict_queryset(
permission=permission_document_view, queryset=self.documents,
user=user
)
def get_full_path(self):
"""
Returns a string that represents the path to the cabinet. The
path string starts from the root cabinet.
"""
result = []
for node in self.get_ancestors(include_self=True):
result.append(node.label)
return ' / '.join(result)
def save(self, *args, **kwargs):
_user = kwargs.pop('_user', None)
with transaction.atomic():
is_new = not self.pk
super(Cabinet, self).save(*args, **kwargs)
if is_new:
event_cabinet_created.commit(
actor=_user, target=self
)
else:
event_cabinet_edited.commit(
actor=_user, target=self
)
def validate_unique(self, exclude=None):
"""
Explicit validation of uniqueness of parent+label as the provided
unique_together check in Meta is not working for all 100% cases
when there is a FK in the unique_together tuple
https://code.djangoproject.com/ticket/1751
"""
with transaction.atomic():
if connection.vendor == 'oracle':
queryset = Cabinet.objects.filter(parent=self.parent, label=self.label)
else:
queryset = Cabinet.objects.select_for_update().filter(parent=self.parent, label=self.label)
if queryset.exists():
params = {
'model_name': _('Cabinet'),
'field_labels': _('Parent and Label')
}
raise ValidationError(
{
NON_FIELD_ERRORS: [
ValidationError(
message=_(
'%(model_name)s with this %(field_labels)s already '
'exists.'
), code='unique_together', params=params,
)
],
},
)
class DocumentCabinet(Cabinet):
"""
Represent a document's cabinet. This Model is a proxy model from Cabinet
and is used as an alias to map columns to it without having to map them
to the base Cabinet model.
"""
class Meta:
proxy = True
verbose_name = _('Document cabinet')
verbose_name_plural = _('Document cabinets')