Convert workflow previews app to use file caching

Signed-off-by: Roberto Rosario <roberto.rosario@mayan-edms.com>
This commit is contained in:
Roberto Rosario
2019-07-26 02:22:04 -04:00
parent f920dffc01
commit 93ba547350
7 changed files with 87 additions and 17 deletions

View File

@@ -27,7 +27,6 @@ from .serializers import (
) )
from .settings import settings_workflow_image_cache_time from .settings import settings_workflow_image_cache_time
from .storages import storage_workflowimagecache
from .tasks import task_generate_workflow_image from .tasks import task_generate_workflow_image
@@ -204,7 +203,8 @@ class APIWorkflowImageView(generics.RetrieveAPIView):
) )
cache_filename = task.get(timeout=WORKFLOW_IMAGE_TASK_TIMEOUT) cache_filename = task.get(timeout=WORKFLOW_IMAGE_TASK_TIMEOUT)
with storage_workflowimagecache.open(cache_filename) as file_object: cache_file = self.get_object().cache_partition.get_file(filename=cache_filename)
with cache_file.open() as file_object:
response = HttpResponse(file_object.read(), content_type='image') response = HttpResponse(file_object.read(), content_type='image')
if '_hash' in request.GET: if '_hash' in request.GET:
patch_cache_control( patch_cache_control(

View File

@@ -1,7 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.apps import apps from django.apps import apps
from django.db.models.signals import post_save from django.db.models.signals import post_migrate, post_save
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from mayan.apps.acls.classes import ModelPermission from mayan.apps.acls.classes import ModelPermission
@@ -25,7 +25,8 @@ from .classes import DocumentStateHelper, WorkflowAction
from .events import event_workflow_created, event_workflow_edited from .events import event_workflow_created, event_workflow_edited
from .dependencies import * # NOQA from .dependencies import * # NOQA
from .handlers import ( from .handlers import (
handler_index_document, handler_launch_workflow, handler_trigger_transition handler_create_workflow_image_cache, handler_index_document,
handler_launch_workflow, handler_trigger_transition
) )
from .html_widgets import WorkflowLogExtraDataWidget, widget_transition_events from .html_widgets import WorkflowLogExtraDataWidget, widget_transition_events
from .links import ( from .links import (
@@ -452,6 +453,10 @@ class DocumentStatesApp(MayanAppConfig):
# Index updating # Index updating
post_migrate.connect(
dispatch_uid='workflows_handler_create_workflow_image_cache',
receiver=handler_create_workflow_image_cache,
)
post_save.connect( post_save.connect(
dispatch_uid='workflows_handler_index_document_save', dispatch_uid='workflows_handler_index_document_save',
receiver=handler_index_document, receiver=handler_index_document,

View File

@@ -6,6 +6,22 @@ from django.utils.translation import ugettext_lazy as _
from mayan.apps.document_indexing.tasks import task_index_document from mayan.apps.document_indexing.tasks import task_index_document
from mayan.apps.events.classes import EventType from mayan.apps.events.classes import EventType
from .literals import (
WORKFLOW_IMAGE_CACHE_STORAGE_INSTANCE_PATH, WORKFLOW_IMAGE_CACHE_NAME
)
from .settings import setting_workflow_image_cache_maximum_size
def handler_create_workflow_image_cache(sender, **kwargs):
Cache = apps.get_model(app_label='file_caching', model_name='Cache')
Cache.objects.update_or_create(
defaults={
'label': _('Workflow images'),
'storage_instance_path': WORKFLOW_IMAGE_CACHE_STORAGE_INSTANCE_PATH,
'maximum_size': setting_workflow_image_cache_maximum_size.value,
}, name=WORKFLOW_IMAGE_CACHE_NAME,
)
def handler_index_document(sender, **kwargs): def handler_index_document(sender, **kwargs):
task_index_document.apply_async( task_index_document.apply_async(

View File

@@ -2,6 +2,8 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
DEFAULT_WORKFLOW_IMAGE_CACHE_MAXIMUM_SIZE = 50 * 2 ** 20 # 50 Megabytes
FIELD_TYPE_CHOICE_CHAR = 1 FIELD_TYPE_CHOICE_CHAR = 1
FIELD_TYPE_CHOICE_INTEGER = 2 FIELD_TYPE_CHOICE_INTEGER = 2
FIELD_TYPE_CHOICES = ( FIELD_TYPE_CHOICES = (
@@ -30,4 +32,6 @@ WORKFLOW_ACTION_WHEN_CHOICES = (
(WORKFLOW_ACTION_ON_ENTRY, _('On entry')), (WORKFLOW_ACTION_ON_ENTRY, _('On entry')),
(WORKFLOW_ACTION_ON_EXIT, _('On exit')), (WORKFLOW_ACTION_ON_EXIT, _('On exit')),
) )
WORKFLOW_IMAGE_CACHE_NAME = 'workflow_images'
WORKFLOW_IMAGE_CACHE_STORAGE_INSTANCE_PATH = 'mayan.apps.document_states.storages.storage_workflowimagecache'
WORKFLOW_IMAGE_TASK_TIMEOUT = 60 WORKFLOW_IMAGE_TASK_TIMEOUT = 60

View File

@@ -7,16 +7,17 @@ import logging
from furl import furl from furl import furl
from graphviz import Digraph from graphviz import Digraph
from django.apps import apps
from django.conf import settings from django.conf import settings
from django.core import serializers from django.core import serializers
from django.core.exceptions import PermissionDenied, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
from django.core.files.base import ContentFile
from django.db import IntegrityError, models, transaction from django.db import IntegrityError, models, transaction
from django.db.models import F, Max, Q from django.db.models import F, Max, Q
from django.urls import reverse from django.urls import reverse
from django.utils.encoding import ( from django.utils.encoding import (
force_bytes, force_text, python_2_unicode_compatible force_bytes, force_text, python_2_unicode_compatible
) )
from django.utils.functional import cached_property
from django.utils.module_loading import import_string from django.utils.module_loading import import_string
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@@ -31,11 +32,11 @@ from .error_logs import error_log_state_actions
from .events import event_workflow_created, event_workflow_edited from .events import event_workflow_created, event_workflow_edited
from .literals import ( from .literals import (
FIELD_TYPE_CHOICES, WIDGET_CLASS_CHOICES, WORKFLOW_ACTION_WHEN_CHOICES, FIELD_TYPE_CHOICES, WIDGET_CLASS_CHOICES, WORKFLOW_ACTION_WHEN_CHOICES,
WORKFLOW_ACTION_ON_ENTRY, WORKFLOW_ACTION_ON_EXIT WORKFLOW_ACTION_ON_ENTRY, WORKFLOW_ACTION_ON_EXIT,
WORKFLOW_IMAGE_CACHE_NAME
) )
from .managers import WorkflowManager from .managers import WorkflowManager
from .permissions import permission_workflow_transition from .permissions import permission_workflow_transition
from .storages import storage_workflowimagecache
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -72,19 +73,37 @@ class Workflow(models.Model):
def __str__(self): def __str__(self):
return self.label return self.label
def generate_image(self): @cached_property
cache_filename = '{}-{}'.format(self.id, self.get_hash()) def cache(self):
image = self.render() Cache = apps.get_model(app_label='file_caching', model_name='Cache')
return Cache.objects.get(name=WORKFLOW_IMAGE_CACHE_NAME)
# Since open "wb+" doesn't create files, check if the file @cached_property
# exists, if not then create it def cache_partition(self):
if not storage_workflowimagecache.exists(cache_filename): partition, created = self.cache.partitions.get_or_create(
storage_workflowimagecache.save( name='{}'.format(self.pk)
name=cache_filename, content=ContentFile(content='') )
return partition
def delete(self, *args, **kwargs):
self.cache_partition.delete()
return super(Workflow, self).delete(*args, **kwargs)
def generate_image(self):
cache_filename = '{}'.format(self.get_hash())
if self.cache_partition.get_file(filename=cache_filename):
logger.debug(
'workflow cache file "%s" found', cache_filename
)
else:
logger.debug(
'workflow cache file "%s" not found', cache_filename
) )
with storage_workflowimagecache.open(cache_filename, mode='wb+') as file_object: image = self.render()
file_object.write(image) with self.cache_partition.create_file(filename=cache_filename) as file_object:
file_object.write(image)
return cache_filename return cache_filename
@@ -107,6 +126,8 @@ class Workflow(models.Model):
Workflow.objects.filter(pk=self.pk) Workflow.objects.filter(pk=self.pk)
) + list( ) + list(
WorkflowState.objects.filter(workflow__pk=self.pk) WorkflowState.objects.filter(workflow__pk=self.pk)
) + list(
WorkflowStateAction.objects.filter(state__workflow__pk=self.pk)
) + list( ) + list(
WorkflowTransition.objects.filter(workflow__pk=self.pk) WorkflowTransition.objects.filter(workflow__pk=self.pk)
) )

View File

@@ -7,8 +7,20 @@ from django.utils.translation import ugettext_lazy as _
from mayan.apps.smart_settings.classes import Namespace from mayan.apps.smart_settings.classes import Namespace
from .literals import DEFAULT_WORKFLOW_IMAGE_CACHE_MAXIMUM_SIZE
from .utils import callback_update_workflow_image_cache_size
namespace = Namespace(label=_('Workflows'), name='document_states') namespace = Namespace(label=_('Workflows'), name='document_states')
setting_workflow_image_cache_maximum_size = namespace.add_setting(
global_name='WORKFLOW_IMAGE_CACHE_MAXIMUM_SIZE',
default=DEFAULT_WORKFLOW_IMAGE_CACHE_MAXIMUM_SIZE,
help_text=_(
'The threshold at which the WORKFLOW_IMAGE_CACHE_STORAGE_BACKEND will '
'start deleting the oldest workflow image cache files. Specify the '
'size in bytes.'
), post_edit_function=callback_update_workflow_image_cache_size
)
settings_workflow_image_cache_time = namespace.add_setting( settings_workflow_image_cache_time = namespace.add_setting(
global_name='WORKFLOWS_IMAGE_CACHE_TIME', default='31556926', global_name='WORKFLOWS_IMAGE_CACHE_TIME', default='31556926',
help_text=_( help_text=_(

View File

@@ -0,0 +1,12 @@
from __future__ import unicode_literals
from django.apps import apps
from .literals import WORKFLOW_IMAGE_CACHE_NAME
def callback_update_workflow_image_cache_size(setting):
Cache = apps.get_model(app_label='file_caching', model_name='Cache')
cache = Cache.objects.get(name=WORKFLOW_IMAGE_CACHE_NAME)
cache.maximum_size = setting.value
cache.save()