diff --git a/apps/scheduler/api.py b/apps/scheduler/api.py index d9fcd0c941..59162e10d0 100644 --- a/apps/scheduler/api.py +++ b/apps/scheduler/api.py @@ -21,4 +21,5 @@ def register_interval_job(name, title, func, weeks=0, days=0, hours=0, minutes=0 def remove_job(name): if name in registered_jobs: scheduler.unschedule_job(registered_jobs[name]['job']) + registered_jobs.pop(name) diff --git a/apps/sources/__init__.py b/apps/sources/__init__.py index 0e6ded885b..d0e29ecd93 100644 --- a/apps/sources/__init__.py +++ b/apps/sources/__init__.py @@ -6,7 +6,8 @@ from navigation.api import register_links, \ from permissions.api import register_permission, set_namespace_title from sources.staging import StagingFile -from sources.models import WebForm, StagingFolder, SourceTransformation +from sources.models import WebForm, StagingFolder, SourceTransformation, \ + WatchFolder from sources.widgets import staging_file_thumbnail PERMISSION_SOURCES_SETUP_VIEW = {'namespace': 'sources_setup', 'name': 'sources_setup_view', 'label': _(u'View existing document sources')} @@ -24,7 +25,8 @@ staging_file_preview = {'text': _(u'preview'), 'class': 'fancybox-noscaling', 'v staging_file_delete = {'text': _(u'delete'), 'view': 'staging_file_delete', 'args': ['source.source_type', 'source.pk', 'object.id'], 'famfam': 'delete', 'keep_query': True} setup_web_form_list = {'text': _(u'web forms'), 'view': 'setup_web_form_list', 'famfam': 'application_form', 'children_classes': [WebForm]} -setup_staging_folder_list = {'text': _(u'staging folders'), 'view': 'setup_staging_folder_list', 'famfam': 'folder_magnify', 'children_classes': [StagingFolder]} +setup_staging_folder_list = {'text': _(u'staging folders'), 'view': 'setup_staging_folder_list', 'famfam': 'folder_camera', 'children_classes': [StagingFolder]} +setup_watch_folder_list = {'text': _(u'watch folders'), 'view': 'setup_watch_folder_list', 'famfam': 'folder_magnify', 'children_classes': [WatchFolder]} setup_source_edit = {'text': _(u'edit'), 'view': 'setup_source_edit', 'args': ['source.source_type', 'source.pk'], 'famfam': 'application_form_edit'} setup_source_delete = {'text': _(u'delete'), 'view': 'setup_source_delete', 'args': ['source.source_type', 'source.pk'], 'famfam': 'application_form_delete'} @@ -41,19 +43,22 @@ register_links(StagingFile, [staging_file_delete]) register_links(SourceTransformation, [setup_source_transformation_edit, setup_source_transformation_delete]) -register_links(['setup_web_form_list', 'setup_staging_folder_list', 'setup_source_create'], [setup_web_form_list, setup_staging_folder_list], menu_name='form_header') +register_links(['setup_web_form_list', 'setup_staging_folder_list', 'setup_watch_folder_list', 'setup_source_create'], [setup_web_form_list, setup_staging_folder_list, setup_watch_folder_list], menu_name='form_header') -register_links(WebForm, [setup_web_form_list, setup_staging_folder_list], menu_name='form_header') +register_links(WebForm, [setup_web_form_list, setup_staging_folder_list, setup_watch_folder_list], menu_name='form_header') register_links(WebForm, [setup_source_transformation_list, setup_source_edit, setup_source_delete]) -register_links(['setup_web_form_list', 'setup_staging_folder_list', 'setup_source_edit', 'setup_source_delete', 'setup_source_create'], [setup_source_create], menu_name='sidebar') +register_links(['setup_web_form_list', 'setup_staging_folder_list', 'setup_watch_folder_list', 'setup_source_edit', 'setup_source_delete', 'setup_source_create'], [setup_source_create], menu_name='sidebar') -register_links(StagingFolder, [setup_web_form_list, setup_staging_folder_list], menu_name='form_header') +register_links(StagingFolder, [setup_web_form_list, setup_staging_folder_list, setup_watch_folder_list], menu_name='form_header') register_links(StagingFolder, [setup_source_transformation_list, setup_source_edit, setup_source_delete]) +register_links(WatchFolder, [setup_web_form_list, setup_staging_folder_list, setup_watch_folder_list], menu_name='form_header') +register_links(WatchFolder, [setup_source_transformation_list, setup_source_edit, setup_source_delete]) + register_links(['setup_source_transformation_create', 'setup_source_transformation_edit', 'setup_source_transformation_delete', 'setup_source_transformation_list'], [setup_source_transformation_create], menu_name='sidebar') -source_views = ['setup_web_form_list', 'setup_staging_folder_list', 'setup_source_edit', 'setup_source_delete', 'setup_source_create', 'setup_source_transformation_list', 'setup_source_transformation_edit', 'setup_source_transformation_delete', 'setup_source_transformation_create'] +source_views = ['setup_web_form_list', 'setup_staging_folder_list', 'setup_watch_folder_list', 'setup_source_edit', 'setup_source_delete', 'setup_source_create', 'setup_source_transformation_list', 'setup_source_transformation_edit', 'setup_source_transformation_delete', 'setup_source_transformation_create'] register_model_list_columns(StagingFile, [ {'name':_(u'thumbnail'), 'attribute': diff --git a/apps/sources/forms.py b/apps/sources/forms.py index 1bc06a6871..3f0b625887 100644 --- a/apps/sources/forms.py +++ b/apps/sources/forms.py @@ -4,7 +4,8 @@ from django.utils.translation import ugettext from documents.forms import DocumentForm -from sources.models import WebForm, StagingFolder, SourceTransformation +from sources.models import WebForm, StagingFolder, SourceTransformation, \ + WatchFolder from sources.widgets import FamFamRadioSelect from sources.utils import validate_whitelist_blacklist @@ -87,6 +88,11 @@ class StagingFolderSetupForm(forms.ModelForm): model = StagingFolder +class WatchFolderSetupForm(forms.ModelForm): + class Meta: + model = WatchFolder + + class SourceTransformationForm(forms.ModelForm): class Meta: model = SourceTransformation diff --git a/apps/sources/literals.py b/apps/sources/literals.py index 4cb2a80640..a1555f4033 100644 --- a/apps/sources/literals.py +++ b/apps/sources/literals.py @@ -41,13 +41,16 @@ SOURCE_ICON_CHOICES = ( SOURCE_CHOICE_WEB_FORM = 'webform' SOURCE_CHOICE_STAGING = 'staging' +SOURCE_CHOICE_WATCH = 'watch' SOURCE_CHOICES = ( (SOURCE_CHOICE_WEB_FORM, _(u'web form')), (SOURCE_CHOICE_STAGING, _(u'server staging folder')), + (SOURCE_CHOICE_WATCH, _(u'server watch folder')), ) SOURCE_CHOICES_PLURAL = ( (SOURCE_CHOICE_WEB_FORM, _(u'web forms')), (SOURCE_CHOICE_STAGING, _(u'server staging folders')), + (SOURCE_CHOICE_WATCH, _(u'server watch folders')), ) diff --git a/apps/sources/models.py b/apps/sources/models.py index 5cb757b042..092fe94ee2 100644 --- a/apps/sources/models.py +++ b/apps/sources/models.py @@ -10,12 +10,13 @@ from documents.models import DocumentType from metadata.models import MetadataType from converter.api import get_available_transformations_choices from converter.literals import DIMENSION_SEPARATOR +from scheduler.api import register_interval_job, remove_job from sources.managers import SourceTransformationManager from sources.literals import SOURCE_CHOICES, SOURCE_CHOICES_PLURAL, \ SOURCE_INTERACTIVE_UNCOMPRESS_CHOICES, SOURCE_CHOICE_WEB_FORM, \ SOURCE_CHOICE_STAGING, SOURCE_ICON_DISK, SOURCE_ICON_DRIVE, \ - SOURCE_ICON_CHOICES + SOURCE_ICON_CHOICES, SOURCE_CHOICE_WATCH, SOURCE_UNCOMPRESS_CHOICES class BaseModel(models.Model): @@ -77,7 +78,7 @@ class StagingFolder(InteractiveBaseModel): class Meta(InteractiveBaseModel.Meta): verbose_name = _(u'staging folder') - verbose_name_plural = _(u'staging folder') + verbose_name_plural = _(u'staging folders') ''' class SourceMetadata(models.Model): @@ -107,6 +108,29 @@ class WebForm(InteractiveBaseModel): verbose_name = _(u'web form') verbose_name_plural = _(u'web forms') +def test(): + print 'WatchFolder' + +class WatchFolder(BaseModel): + is_interactive = False + source_type = SOURCE_CHOICE_WATCH + + folder_path = models.CharField(max_length=255, verbose_name=_(u'folder path'), help_text=_(u'Server side filesystem path.')) + uncompress = models.CharField(max_length=1, choices=SOURCE_UNCOMPRESS_CHOICES, verbose_name=_(u'uncompress'), help_text=_(u'Whether to expand or not compressed archives.')) + delete_after_upload = models.BooleanField(default=True, verbose_name=_(u'delete after upload'), help_text=_(u'Delete the file after is has been successfully uploaded.')) + interval = models.PositiveIntegerField(verbose_name=_(u'interval'), help_text=_(u'Inverval in seconds where the watch folder path is checked for new documents.')) + + def save(self, *args, **kwargs): + if self.pk: + remove_job('watch_folder_%d' % self.pk) + super(WatchFolder, self).save(*args, **kwargs) + if self.enabled: + register_interval_job('watch_folder_%d' % self.pk, self.fullname(), test, seconds=self.interval) + + class Meta(BaseModel.Meta): + verbose_name = _(u'watch folder') + verbose_name_plural = _(u'watch folders') + class ArgumentsValidator(object): message = _(u'Enter a valid value.') diff --git a/apps/sources/urls.py b/apps/sources/urls.py index 4e879e5096..b75573382d 100644 --- a/apps/sources/urls.py +++ b/apps/sources/urls.py @@ -1,6 +1,7 @@ from django.conf.urls.defaults import patterns, url -from sources.literals import SOURCE_CHOICE_WEB_FORM, SOURCE_CHOICE_STAGING +from sources.literals import SOURCE_CHOICE_WEB_FORM, SOURCE_CHOICE_STAGING, \ + SOURCE_CHOICE_WATCH urlpatterns = patterns('sources.views', url(r'^staging_file/type/(?P\w+)/(?P\d+)/(?P\w+)/preview/$', 'staging_file_preview', (), 'staging_file_preview'), @@ -13,8 +14,8 @@ urlpatterns = patterns('sources.views', #Setup views url(r'^setup/interactive/%s/list/$' % SOURCE_CHOICE_WEB_FORM, 'setup_source_list', {'source_type': SOURCE_CHOICE_WEB_FORM}, 'setup_web_form_list'), - url(r'^setup/interactive/%s/list/$' % SOURCE_CHOICE_STAGING, 'setup_source_list', {'source_type': SOURCE_CHOICE_STAGING}, 'setup_staging_folder_list'), + url(r'^setup/interactive/%s/list/$' % SOURCE_CHOICE_WATCH, 'setup_source_list', {'source_type': SOURCE_CHOICE_WATCH}, 'setup_watch_folder_list'), url(r'^setup/interactive/(?P\w+)/list/$', 'setup_source_list', (), 'setup_source_list'), url(r'^setup/interactive/(?P\w+)/(?P\d+)/edit/$', 'setup_source_edit', (), 'setup_source_edit'), diff --git a/apps/sources/views.py b/apps/sources/views.py index 35c7fcc2ff..daa1e82d56 100644 --- a/apps/sources/views.py +++ b/apps/sources/views.py @@ -26,12 +26,15 @@ from metadata.api import save_metadata_list, \ from permissions.api import check_permissions import sendfile -from sources.models import WebForm, StagingFolder, SourceTransformation -from sources.literals import SOURCE_CHOICE_WEB_FORM, SOURCE_CHOICE_STAGING +from sources.models import WebForm, StagingFolder, SourceTransformation, \ + WatchFolder +from sources.literals import SOURCE_CHOICE_WEB_FORM, SOURCE_CHOICE_STAGING, \ + SOURCE_CHOICE_WATCH from sources.literals import SOURCE_UNCOMPRESS_CHOICE_Y, \ SOURCE_UNCOMPRESS_CHOICE_ASK from sources.staging import create_staging_file_class, StagingFile -from sources.forms import StagingDocumentForm, WebFormForm +from sources.forms import StagingDocumentForm, WebFormForm, \ + WatchFolderSetupForm from sources.forms import WebFormSetupForm, StagingFolderSetupForm from sources.forms import SourceTransformationForm, SourceTransformationForm_create from sources import PERMISSION_SOURCES_SETUP_VIEW, \ @@ -369,6 +372,8 @@ def setup_source_list(request, source_type): cls = WebForm elif source_type == SOURCE_CHOICE_STAGING: cls = StagingFolder + elif source_type == SOURCE_CHOICE_WATCH: + cls = WatchFolder context = { 'object_list': cls.objects.all(), @@ -391,6 +396,9 @@ def setup_source_edit(request, source_type, source_id): elif source_type == SOURCE_CHOICE_STAGING: cls = StagingFolder form_class = StagingFolderSetupForm + elif source_type == SOURCE_CHOICE_WATCH: + cls = WatchFolder + form_class = WatchFolderSetupForm source = get_object_or_404(cls, pk=source_id) next = request.POST.get('next', request.GET.get('next', request.META.get('HTTP_REFERER', '/'))) @@ -429,7 +437,11 @@ def setup_source_delete(request, source_type, source_id): cls = StagingFolder form_icon = u'folder_delete.png' redirect_view = 'setup_staging_folder_list' - + elif source_type == SOURCE_CHOICE_WATCH: + cls = WatchFolder + form_icon = u'folder_delete.png' + redirect_view = 'setup_watch_folder_list' + redirect_view = reverse('setup_source_list', args=[source_type]) previous = request.POST.get('previous', request.GET.get('previous', request.META.get('HTTP_REFERER', redirect_view))) @@ -470,7 +482,10 @@ def setup_source_create(request, source_type): elif source_type == SOURCE_CHOICE_STAGING: cls = StagingFolder form_class = StagingFolderSetupForm - + elif source_type == SOURCE_CHOICE_WATCH: + cls = WatchFolder + form_class = WatchFolderSetupForm + if request.method == 'POST': form = form_class(data=request.POST) if form.is_valid(): @@ -500,6 +515,8 @@ def setup_source_transformation_list(request, source_type, source_id): cls = WebForm elif source_type == SOURCE_CHOICE_STAGING: cls = StagingFolder + elif source_type == SOURCE_CHOICE_WATCH: + cls = WatchFolder source = get_object_or_404(cls, pk=source_id) @@ -597,7 +614,9 @@ def setup_source_transformation_create(request, source_type, source_id): cls = WebForm elif source_type == SOURCE_CHOICE_STAGING: cls = StagingFolder - + elif source_type == SOURCE_CHOICE_WATCH: + cls = WatchFolder + source = get_object_or_404(cls, pk=source_id) redirect_view = reverse('setup_source_transformation_list', args=[source.source_type, source.pk]) diff --git a/apps/sources/watch_folders.py b/apps/sources/watch_folders.py new file mode 100644 index 0000000000..205bd30655 --- /dev/null +++ b/apps/sources/watch_folders.py @@ -0,0 +1,21 @@ + + + if staging_folder.uncompress == SOURCE_UNCOMPRESS_CHOICE_Y: + expand = True + else: + expand = False + transformations, errors = SourceTransformation.transformations.get_for_object_as_list(staging_folder) + if (not expand) or (expand and not _handle_zip_file(request, staging_file.upload(), document_type=document_type, transformations=transformations)): + document = Document(file=staging_file.upload()) + if document_type: + document.document_type = document_type + document.save() + document.apply_default_transformations(transformations) + _handle_save_document(request, document, form) + messages.success(request, _(u'Staging file: %s, uploaded successfully.') % staging_file.filename) + + if staging_folder.delete_after_upload: + staging_file.delete(preview_size=staging_folder.get_preview_size(), transformations=transformations) + messages.success(request, _(u'Staging file: %s, deleted successfully.') % staging_file.filename) + except Exception, e: + messages.error(request, e)