diff --git a/HISTORY.rst b/HISTORY.rst index 8c84096700..0031a0e265 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -51,6 +51,7 @@ Next (2018-XX-XX) - Move the page count display to the top of the image. - Unify the way to gather the project's metadata. Use mayan.__XX__ and a new common tag named {% project_information '' %} - Return to the same source view after uploading a document. +- Add new WizardStep class to decouple the wizard step configuration. 2.8 (2018-02-27) ================ diff --git a/mayan/apps/sources/literals.py b/mayan/apps/sources/literals.py index b16d9480e6..aab8d4b02b 100644 --- a/mayan/apps/sources/literals.py +++ b/mayan/apps/sources/literals.py @@ -64,8 +64,3 @@ DEFAULT_METADATA_ATTACHMENT_NAME = 'metadata.yaml' DEFAULT_POP3_TIMEOUT = 60 DEFAULT_IMAP_MAILBOX = 'INBOX' DEFAULT_SOURCE_TASK_RETRY_DELAY = 10 - -# Upload wizard steps -STEP_DOCUMENT_TYPE = '0' -STEP_METADATA = '1' -STEP_TAGS = '2' diff --git a/mayan/apps/sources/views.py b/mayan/apps/sources/views.py index ea47b18a37..a2164f2077 100644 --- a/mayan/apps/sources/views.py +++ b/mayan/apps/sources/views.py @@ -242,18 +242,20 @@ class UploadInteractiveView(UploadBaseView): except Exception as exception: messages.error(self.request, exception) - task_source_handle_upload.apply_async(kwargs=dict( - description=forms['document_form'].cleaned_data.get('description'), - document_type_id=self.document_type.pk, - expand=expand, - label=label, - language=forms['document_form'].cleaned_data.get('language'), - metadata_dict_list=decode_metadata_from_url(self.request.GET), - shared_uploaded_file_id=shared_uploaded_file.pk, - source_id=self.source.pk, - tag_ids=self.request.GET.getlist('tags'), - user_id=user_id, - )) + task_source_handle_upload.apply_async( + kwargs=dict( + description=forms['document_form'].cleaned_data.get('description'), + document_type_id=self.document_type.pk, + expand=expand, + label=label, + language=forms['document_form'].cleaned_data.get('language'), + metadata_dict_list=decode_metadata_from_url(self.request.GET), + shared_uploaded_file_id=shared_uploaded_file.pk, + source_id=self.source.pk, + tag_ids=self.request.GET.getlist('tags'), + user_id=user_id, + ) + ) messages.success( self.request, _( diff --git a/mayan/apps/sources/wizards.py b/mayan/apps/sources/wizards.py index 9bcc9fe1d8..f023226722 100644 --- a/mayan/apps/sources/wizards.py +++ b/mayan/apps/sources/wizards.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals from django.contrib import messages from django.http import HttpResponseRedirect from django.urls import reverse +from django.utils.decorators import classonlymethod from django.utils.encoding import force_text from django.utils.http import urlencode from django.utils.translation import ugettext_lazy as _ @@ -13,37 +14,163 @@ from common.mixins import ViewPermissionCheckMixin from documents.forms import DocumentTypeSelectForm from metadata.forms import DocumentMetadataFormSet from tags.forms import TagMultipleSelectionForm +from tags.models import Tag -from .literals import STEP_DOCUMENT_TYPE, STEP_METADATA, STEP_TAGS from .models import InteractiveSource -def has_metadata_types(wizard): - """ - Skip the 2nd step if document type has no associated metadata - """ - cleaned_data = wizard.get_cleaned_data_for_step(STEP_DOCUMENT_TYPE) or {} +class WizardStep(object): + _registry = {} - document_type = cleaned_data.get('document_type') + @classmethod + def done(cls, wizard): + return {} - if document_type: - return document_type.metadata.exists() + @classmethod + def get(cls, name): + return cls._registry[name] + + @classmethod + def get_all(cls): + return sorted( + cls._registry.values(), key=lambda x: x.number + ) + + @classmethod + def get_choices(cls, attribute_name): + return sorted( + [ + (step.name, getattr(step, attribute_name)) for step in cls.get_all() + ] + ) + + @classmethod + def get_form_initial(cls, wizard): + return {} + + @classmethod + def get_form_kwargs(cls, wizard): + return {} + + @classmethod + def register(cls, step): + cls._registry[step.name] = step + + +class WizardStepDocumentType(WizardStep): + form_class = DocumentTypeSelectForm + label = _('Select document type') + name = 'document_type_selection' + number = 0 + + @classmethod + def condition(cls, wizard): + return True + + @classmethod + def done(cls, wizard): + cleaned_data = wizard.get_cleaned_data_for_step(cls.name) + if cleaned_data: + return { + 'document_type_id': cleaned_data['document_type'].pk + } + + @classmethod + def get_form_kwargs(cls, wizard): + return {'user': wizard.request.user} + + +class WizardStepMetadata(WizardStep): + form_class = DocumentMetadataFormSet + label = _('Enter document metadata') + name = 'metadata_entry' + number = 1 + + @classmethod + def condition(cls, wizard): + """ + Skip step if document type has no associated metadata + """ + cleaned_data = wizard.get_cleaned_data_for_step(WizardStepDocumentType.name) or {} + + document_type = cleaned_data.get('document_type') + + if document_type: + return document_type.metadata.exists() + + @classmethod + def get_form_initial(cls, wizard): + initial = [] + + step_data = wizard.get_cleaned_data_for_step(WizardStepDocumentType.name) + if step_data: + document_type = step_data['document_type'] + for document_type_metadata_type in document_type.metadata.all(): + initial.append( + { + 'document_type': document_type, + 'metadata_type': document_type_metadata_type.metadata_type, + } + ) + + return initial + + @classmethod + def done(cls, wizard): + result = {} + cleaned_data = wizard.get_cleaned_data_for_step(cls.name) + if cleaned_data: + for identifier, metadata in enumerate(wizard.get_cleaned_data_for_step(cls.name)): + if metadata.get('update'): + result['metadata%s_id' % identifier] = metadata['id'] + result['metadata%s_value' % identifier] = metadata['value'] + + return result + + +class WizardStepTags(WizardStep): + form_class = TagMultipleSelectionForm + label = _('Select tags') + name = 'tag_selection' + number = 2 + + @classmethod + def condition(cls, wizard): + return Tag.objects.exists() + + @classmethod + def get_form_kwargs(self, wizard): + return { + 'help_text': _('Tags to be attached.'), + 'user': wizard.request.user + } + + @classmethod + def done(cls, wizard): + result = {} + cleaned_data = wizard.get_cleaned_data_for_step(cls.name) + if cleaned_data: + result['tags'] = [ + force_text(tag.pk) for tag in cleaned_data['tags'] + ] + + return result + + +WizardStep.register(WizardStepDocumentType) +WizardStep.register(WizardStepMetadata) +WizardStep.register(WizardStepTags) class DocumentCreateWizard(ViewPermissionCheckMixin, SessionWizardView): - condition_dict = {STEP_METADATA: has_metadata_types} - extra_context = {} - form_list = ( - DocumentTypeSelectForm, DocumentMetadataFormSet, - TagMultipleSelectionForm - ) - form_titles = { - DocumentTypeSelectForm: _('Step 1 of 3: Select document type'), - DocumentMetadataFormSet: _('Step 2 of 3: Enter document metadata'), - TagMultipleSelectionForm: _('Step 3 of 3: Select tags'), - } template_name = 'appearance/generic_wizard.html' + @classonlymethod + def as_view(cls, *args, **kwargs): + cls.form_list = WizardStep.get_choices(attribute_name='form_class') + cls.condition_dict = dict(WizardStep.get_choices(attribute_name='condition')) + return super(DocumentCreateWizard, cls).as_view(*args, **kwargs) + def dispatch(self, request, *args, **kwargs): if not InteractiveSource.objects.filter(enabled=True).exists(): messages.error( @@ -64,8 +191,15 @@ class DocumentCreateWizard(ViewPermissionCheckMixin, SessionWizardView): DocumentCreateWizard, self ).get_context_data(form=form, **kwargs) + wizard_step = WizardStep.get(name=self.steps.current) + context.update({ - 'step_title': self.form_titles[form.__class__], + 'step_title': _( + 'Step %(step)d of %(total_steps)d: %(step_label)s' + ) % { + 'step': self.steps.step1, 'total_steps': len(self.form_list), + 'step_label': wizard_step.label, + }, 'submit_label': _('Next step'), 'submit_icon': 'fa fa-arrow-right', 'title': _('Document upload wizard'), @@ -73,55 +207,16 @@ class DocumentCreateWizard(ViewPermissionCheckMixin, SessionWizardView): return context def get_form_initial(self, step): - if step == STEP_METADATA: - initial = [] - - for document_type_metadata_type in self.get_cleaned_data_for_step(STEP_DOCUMENT_TYPE)['document_type'].metadata.all(): - initial.append( - { - 'document_type': self.get_cleaned_data_for_step(STEP_DOCUMENT_TYPE)['document_type'], - 'metadata_type': document_type_metadata_type.metadata_type, - } - ) - - return initial - return self.initial_dict.get(step, {}) + return WizardStep.get(name=step).get_form_initial(wizard=self) or {} def get_form_kwargs(self, step): - # Tags form needs the user instance to determine which tags to - # display - if step == STEP_DOCUMENT_TYPE: - return {'user': self.request.user} + return WizardStep.get(name=step).get_form_kwargs(wizard=self) or {} - if step == STEP_TAGS: - return { - 'help_text': _('Tags to be attached.'), - 'user': self.request.user - } - - return {} - - def done(self, *args, **kwargs): + def done(self, form_list, **kwargs): query_dict = {} - try: - query_dict['document_type_id'] = self.get_cleaned_data_for_step(STEP_DOCUMENT_TYPE)['document_type'].pk - except AttributeError: - pass - - try: - for identifier, metadata in enumerate(self.get_cleaned_data_for_step(STEP_METADATA)): - if metadata.get('update'): - query_dict['metadata%s_id' % identifier] = metadata['id'] - query_dict['metadata%s_value' % identifier] = metadata['value'] - except TypeError: - pass - - try: - query_dict['tags'] = ([force_text(tag.pk) for tag in self.get_cleaned_data_for_step(STEP_TAGS)['tags']]) - - except AttributeError: - pass + for step in WizardStep.get_all(): + query_dict.update(step.done(wizard=self) or {}) url = '?'.join( [