Add support for document retention policies. Closes gh-issue #189.

This commit is contained in:
Roberto Rosario
2015-07-04 02:25:59 -04:00
parent 589874bec1
commit ee6bb866c9
9 changed files with 159 additions and 20 deletions

View File

@@ -61,6 +61,8 @@ What's new in Mayan EDMS v2.0
* Simplification of permissions/ACLS and role system
* Removal of the ImageMagick and GraphicsMagick converter backends
* Remove support for applying roles to new users automatically
* Trash can feature
* Retention policies, auto move to trash, auto delete from trash
Upgrading from a previous version
=================================

View File

@@ -0,0 +1,9 @@
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _
TIME_DELTA_UNIT_CHOICES = (
('days', _('Days')),
('hours', _('Hours')),
('minutes', _('Minutes')),
)

View File

@@ -3,7 +3,7 @@ from __future__ import unicode_literals
from django.contrib import admin
from .models import (
Document, DocumentPage, DocumentType, DocumentTypeFilename,
DeletedDocument, Document, DocumentPage, DocumentType, DocumentTypeFilename,
DocumentVersion, RecentDocument
)
@@ -15,13 +15,6 @@ class DocumentPageInline(admin.StackedInline):
allow_add = True
class DocumentVersionInline(admin.StackedInline):
model = DocumentVersion
extra = 1
classes = ('collapse-open',)
allow_add = True
class DocumentTypeFilenameInline(admin.StackedInline):
model = DocumentTypeFilename
extra = 1
@@ -29,27 +22,46 @@ class DocumentTypeFilenameInline(admin.StackedInline):
allow_add = True
class DocumentTypeAdmin(admin.ModelAdmin):
inlines = [
DocumentTypeFilenameInline
]
class DocumentVersionInline(admin.StackedInline):
model = DocumentVersion
extra = 1
classes = ('collapse-open',)
allow_add = True
class DeletedDocumentAdmin(admin.ModelAdmin):
date_hierarchy = 'deleted_date_time'
list_filter = ('document_type',)
list_display = ('uuid', 'label', 'document_type', 'deleted_date_time')
readonly_fields = ('uuid', 'document_type')
class DocumentAdmin(admin.ModelAdmin):
date_hierarchy = 'date_added'
inlines = [
DocumentVersionInline
]
list_display = ('uuid', 'label',)
list_filter = ('document_type',)
list_display = ('uuid', 'label', 'document_type', 'date_added')
readonly_fields = ('uuid', 'document_type', 'date_added')
class DocumentTypeAdmin(admin.ModelAdmin):
inlines = (
DocumentTypeFilenameInline,
)
list_display = ('name', 'trash_time_period', 'trash_time_unit', 'delete_time_period', 'delete_time_unit')
class RecentDocumentAdmin(admin.ModelAdmin):
model = RecentDocument
list_display = ('user', 'document', 'datetime_accessed')
readonly_fields = ('user', 'document', 'datetime_accessed')
list_filter = ('user',)
date_hierarchy = 'datetime_accessed'
list_display = ('user', 'document', 'datetime_accessed')
list_display_links = ('document', 'datetime_accessed')
list_filter = ('user',)
readonly_fields = ('user', 'document', 'datetime_accessed')
admin.site.register(DeletedDocument, DeletedDocumentAdmin)
admin.site.register(Document, DocumentAdmin)
admin.site.register(DocumentType, DocumentTypeAdmin)
admin.site.register(RecentDocument, RecentDocumentAdmin)

View File

@@ -1,5 +1,7 @@
from __future__ import absolute_import, unicode_literals
from datetime import timedelta
from django.utils.translation import ugettext_lazy as _
from actstream import registry
@@ -21,6 +23,7 @@ from converter.permissions import (
permission_transformation_view,
)
from events.permissions import permission_events_view
from mayan.celery import app
from navigation import SourceColumn
from rest_api.classes import APIEndPoint
from statistics.classes import StatisticNamespace
@@ -51,6 +54,7 @@ from .links import (
link_document_version_download, link_document_version_list,
link_document_version_revert, link_trash_can_empty
)
from .literals import CHECK_DELETE_PERIOD_INTERVAL, CHECK_TRASH_PERIOD_INTERVAL
from .models import (
DeletedDocument, Document, DocumentPage, DocumentType, DocumentTypeFilename,
DocumentVersion
@@ -99,6 +103,22 @@ class DocumentsApp(MayanAppConfig):
SourceColumn(source=DeletedDocument, label=_('Type'), attribute='document_type')
SourceColumn(source=DeletedDocument, label=_('Date time trashed'), attribute='deleted_date_time')
app.conf.CELERYBEAT_SCHEDULE.update({
'task_check_trash_periods': {
'task': 'documents.tasks.task_check_trash_periods',
'schedule': timedelta(seconds=CHECK_TRASH_PERIOD_INTERVAL),
'options': {'queue': 'documents'}
},
})
app.conf.CELERYBEAT_SCHEDULE.update({
'task_check_delete_periods': {
'task': 'documents.tasks.task_check_delete_periods',
'schedule': timedelta(seconds=CHECK_DELETE_PERIOD_INTERVAL),
'options': {'queue': 'documents'}
},
})
menu_front_page.bind_links(links=[link_document_list_recent, link_document_list, link_document_list_deleted])
menu_setup.bind_links(links=[link_document_type_setup])
menu_tools.bind_links(links=[link_clear_image_cache])

View File

@@ -100,7 +100,7 @@ class DocumentTypeForm(forms.ModelForm):
Model class form to create or edit a document type
"""
class Meta:
fields = ('name',)
fields = ('name', 'trash_time_period', 'trash_time_unit', 'delete_time_period', 'delete_time_unit')
model = DocumentType

View File

@@ -1,4 +1,8 @@
from __future__ import unicode_literals
CHECK_DELETE_PERIOD_INTERVAL = 60
CHECK_TRASH_PERIOD_INTERVAL = 60
DEFAULT_DELETE_PERIOD = 30
DEFAULT_DELETE_TIME_UNIT = 'days'
DEFAULT_ZIP_FILENAME = 'document_bundle.zip'
DOCUMENT_IMAGE_TASK_TIMEOUT = 20

View File

@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('documents', '0010_auto_20150704_0054'),
]
operations = [
migrations.AddField(
model_name='documenttype',
name='delete_time_period',
field=models.PositiveIntegerField(default=30, verbose_name='Delete time period'),
preserve_default=True,
),
migrations.AddField(
model_name='documenttype',
name='delete_time_unit',
field=models.CharField(default='days', max_length=8, verbose_name='Delete time unit', choices=[('days', 'Days'), ('hours', 'Hours'), ('minutes', 'Minutes'), ('seconds', 'Seconds')]),
preserve_default=True,
),
migrations.AddField(
model_name='documenttype',
name='trash_time_period',
field=models.PositiveIntegerField(null=True, verbose_name='Trash time period', blank=True),
preserve_default=True,
),
migrations.AddField(
model_name='documenttype',
name='trash_time_unit',
field=models.CharField(blank=True, max_length=8, null=True, verbose_name='Trash time unit', choices=[('days', 'Days'), ('hours', 'Hours'), ('minutes', 'Minutes'), ('seconds', 'Seconds')]),
preserve_default=True,
),
migrations.AlterField(
model_name='document',
name='deleted_date_time',
field=models.DateTimeField(verbose_name='Date and time trashed', blank=True),
preserve_default=True,
),
]

View File

@@ -13,6 +13,7 @@ from django.utils.encoding import python_2_unicode_compatible
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from common.literals import TIME_DELTA_UNIT_CHOICES
from common.settings import setting_temporary_directory
from common.utils import fs_cleanup
from converter import (
@@ -28,6 +29,7 @@ from .events import (
event_document_create, event_document_new_version,
event_document_version_revert
)
from .literals import DEFAULT_DELETE_PERIOD, DEFAULT_DELETE_TIME_UNIT
from .managers import (
DocumentManager, DocumentTypeManager, PassthroughManager,
RecentDocumentManager, TrashCanManager
@@ -53,7 +55,11 @@ class DocumentType(models.Model):
Define document types or classes to which a specific set of
properties can be attached
"""
name = models.CharField(max_length=32, verbose_name=_('Name'), unique=True)
name = models.CharField(max_length=32, unique=True, verbose_name=_('Name'))
trash_time_period = models.PositiveIntegerField(blank=True, help_text=_('Amount of time after which documents of this type will be moved to the trash.'), null=True, verbose_name=_('Trash time period'))
trash_time_unit = models.CharField(blank=True, choices=TIME_DELTA_UNIT_CHOICES, null=True, max_length=8, verbose_name=_('Trash time unit'))
delete_time_period = models.PositiveIntegerField(default=DEFAULT_DELETE_PERIOD, help_text=_('Amount of time after which documents of this type in the trash will be deleted.'), verbose_name=_('Delete time period'))
delete_time_unit = models.CharField(choices=TIME_DELTA_UNIT_CHOICES, default=DEFAULT_DELETE_TIME_UNIT, max_length=8, verbose_name=_('Delete time unit'))
objects = DocumentTypeManager()

View File

@@ -1,15 +1,19 @@
from __future__ import unicode_literals
from datetime import timedelta
import logging
from django.contrib.auth.models import User
from django.core.files import File
from django.utils.timezone import now
from mayan.celery import app
from common.models import SharedUploadedFile
from .models import Document, DocumentPage, DocumentType, DocumentVersion
from .models import (
DeletedDocument, Document, DocumentPage, DocumentType, DocumentVersion
)
logger = logging.getLogger(__name__)
@@ -77,3 +81,41 @@ def task_upload_new_version(document_id, shared_uploaded_file_id, user_id, comme
logger.info('Warning during attempt to create new document version for document:%s ; %s', document, warning)
finally:
shared_file.delete()
@app.task(ignore_result=True)
def task_check_trash_periods():
logger.info('Executing')
for document_type in DocumentType.objects.all():
logger.info('Checking trash period of document type: %s', document_type)
if document_type.trash_time_period and document_type.trash_time_unit:
delta = timedelta(**{document_type.trash_time_unit: document_type.trash_time_period})
logger.info('Document type: %s, has a trash period delta of: %s', document_type, delta)
for document in Document.objects.filter(document_type=document_type):
if now() > document.date_added + delta:
logger.info('Document "%s" with id: %d, added on: %s, exceded trash period', document, document.pk, document.date_added)
document.delete()
else:
logger.info('Document type: %s, has a no retention delta', document_type)
logger.info('Finshed')
@app.task(ignore_result=True)
def task_check_delete_periods():
logger.info('Executing')
for document_type in DocumentType.objects.all():
logger.info('Checking deletion period of document type: %s', document_type)
if document_type.delete_time_period and document_type.delete_time_unit:
delta = timedelta(**{document_type.delete_time_unit: document_type.delete_time_period})
logger.info('Document type: %s, has a deletion period delta of: %s', document_type, delta)
for document in DeletedDocument.objects.filter(document_type=document_type):
if now() > document.deleted_date_time + delta:
logger.info('Document "%s" with id: %d, trashed on: %s, exceded delete period', document, document.pk, document.deleted_date_time)
document.delete()
else:
logger.info('Document type: %s, has a no retention delta', document_type)
logger.info('Finshed')