diff --git a/oneanddone/tasks/forms.py b/oneanddone/tasks/forms.py index caac6b68..13275eeb 100644 --- a/oneanddone/tasks/forms.py +++ b/oneanddone/tasks/forms.py @@ -21,6 +21,21 @@ class Meta: fields = ('text',) +class PreviewConfirmationForm(forms.Form): + """ Used to create a multi-step object-creation/update process, with a + chance to preview and cancel changes before committing them. + """ + submission_stages = ('fill', 'preview', 'confirm') + stage = forms.CharField(widget=forms.HiddenInput()) + + def clean(self): + cleaned_data = super(PreviewConfirmationForm, self).clean() + if cleaned_data.get('stage') not in self.submission_stages: + raise forms.ValidationError(_('Form data is missing or has been ' + 'tampered.')) + return cleaned_data + + class TaskInvalidationCriterionForm(forms.Form): criterion = forms.ModelChoiceField(queryset= TaskInvalidationCriterion.objects.all()) diff --git a/oneanddone/tasks/templates/tasks/confirmation.html b/oneanddone/tasks/templates/tasks/confirmation.html index fbd56114..5d1e01e2 100644 --- a/oneanddone/tasks/templates/tasks/confirmation.html +++ b/oneanddone/tasks/templates/tasks/confirmation.html @@ -17,13 +17,13 @@

{{ title }}

{{ csrf() }} {{ hiddenform([batch_form, task_form], [criterion_formset]) }} - + {{ stage_form__fill }}
{{ csrf() }} {{ hiddenform([batch_form, task_form], [criterion_formset]) }} - + {{ stage_form__confirm }}
diff --git a/oneanddone/tasks/templates/tasks/form.html b/oneanddone/tasks/templates/tasks/form.html index 749b4353..9f6c3b71 100644 --- a/oneanddone/tasks/templates/tasks/form.html +++ b/oneanddone/tasks/templates/tasks/form.html @@ -47,7 +47,7 @@

{{ title }}

{% endif %} {% if action == 'Import' %} - + {{ stage_form__preview }} {{ form_field(batch_form['description'], help_text=True) }} {{ form_field(batch_form['query'], help_text=True) }}
diff --git a/oneanddone/tasks/views.py b/oneanddone/tasks/views.py index b0d613c0..72c817d2 100644 --- a/oneanddone/tasks/views.py +++ b/oneanddone/tasks/views.py @@ -17,7 +17,8 @@ from oneanddone.base.util import get_object_or_none, SortHeaders from oneanddone.tasks.filters import ActivityFilterSet, TasksFilterSet -from oneanddone.tasks.forms import (FeedbackForm, TaskImportBatchForm, +from oneanddone.tasks.forms import (FeedbackForm, PreviewConfirmationForm, + TaskImportBatchForm, TaskInvalidCriteriaFormSet, TaskForm) from oneanddone.tasks.mixins import (APIRecordCreatorMixin, APIOnlyCreatorMayDeleteMixin) @@ -219,18 +220,24 @@ def get_forms(self): kwargs = {'initial': None} if self.request.method == 'POST': kwargs['data'] = self.request.POST - - batch_form = TaskImportBatchForm(instance=None, - prefix='batch', **kwargs) + batch_form = TaskImportBatchForm(instance=None, prefix='batch', + **kwargs) criterion_formset = TaskInvalidCriteriaFormSet(prefix='criterion', **kwargs) kwargs['initial'] = {'end_date': date.today() + timedelta(days=30), 'repeatable': False} task_form = TaskForm(instance=None, prefix='task', **kwargs) - return {'criterion_formset': criterion_formset, - 'batch_form': batch_form, - 'task_form': task_form} + forms = {'criterion_formset': criterion_formset, + 'batch_form': batch_form, + 'task_form': task_form} + + # Create a hidden form for each possible PreviewConfirmationForm stage. + # These forms are used to signal what the next stage should be. + make_stage = lambda x: PreviewConfirmationForm(data={'stage': x}) + stages = PreviewConfirmationForm.submission_stages + forms.update({'stage_form__' + s: make_stage(s) for s in stages}) + return forms def get_context_data(self, **kwargs): ctx = super(ImportTasksView, self).get_context_data(**kwargs) @@ -253,20 +260,20 @@ def forms_valid(self, forms): return self.render_to_response(self.get_context_data(**ctx)) def forms_invalid(self, forms): - self.stage = 'initial' + self.stage = 'fill' return self.render_to_response(self.get_context_data(**forms)) def get(self, request, *args, **kwargs): # Assume this is a fresh start to the import process - self.stage = None + self.stage = 'fill' forms = self.get_forms() assert forms['criterion_formset'].total_form_count() == 1 return self.render_to_response(self.get_context_data(**forms)) def post(self, request, *args, **kwargs): - stage = self._update_stage() + self.stage = self.request.POST.get('stage', 'fill') forms = self.get_forms() - if stage == 'initial': + if self.stage == 'fill': return self.render_to_response(self.get_context_data(**forms)) if all([form.is_valid() for form in forms.values()]): @@ -277,8 +284,8 @@ def post(self, request, *args, **kwargs): def done(self, forms): bugs = forms['batch_form'].cleaned_data['_fresh_bugs'] import_batch = forms['batch_form'].save(self.request.user) - criterion_objs = [criterion_form.cleaned_data['criterion'] for criterion_form in - forms['criterion_formset'].forms] + criterion_objs = [criterion_form.cleaned_data['criterion'] for + criterion_form in forms['criterion_formset'].forms] for criterion in criterion_objs: criterion.batches.add(import_batch) criterion.save() @@ -300,12 +307,6 @@ def done(self, forms): messages.success(self.request, _('{num} tasks created.').format(num=str(len(bugs)))) return redirect('tasks.list') - def _update_stage(self): - self.stage = self.request.POST.get('stage') - if self.stage not in ['initial', 'preview', 'confirm']: - raise ValidationError(_('Form data is missing or has been tampered.')) - return self.stage - class CreateTaskView(LoginRequiredMixin, MyStaffUserRequiredMixin, generic.CreateView): model = Task