From e601162b395c806ca0e96851711da7be6f9c6af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Sun, 20 Dec 2015 12:29:48 +0100 Subject: [PATCH] workflows: implement status change after "editable" action (#9329) --- tests/test_form_pages.py | 25 ++++++++++- wcs/backoffice/management.py | 18 +------- wcs/backoffice/submission.py | 6 +-- wcs/forms/common.py | 24 ++++++++++ wcs/forms/root.py | 101 ++++++++++++++++++++----------------------- wcs/workflows.py | 2 +- 6 files changed, 100 insertions(+), 76 deletions(-) diff --git a/tests/test_form_pages.py b/tests/test_form_pages.py index fc79f22..3cee080 100644 --- a/tests/test_form_pages.py +++ b/tests/test_form_pages.py @@ -931,7 +931,7 @@ def test_form_multi_page_post_edit(pub): assert 'barXYZ' in page.body resp = page.forms[0].submit('button_editable') - assert resp.location == 'http://example.net/test/%s/wfedit' % data_id + assert resp.location.startswith('http://example.net/test/%s/wfedit-' % data_id) resp = resp.follow() assert resp.forms[0]['f1'].value == 'foo' resp.forms[0]['f1'] = 'foo2' @@ -948,6 +948,29 @@ def test_form_multi_page_post_edit(pub): assert 'foo2' in resp.body # modified value is there assert 'barXYZ' in resp.body # unchanged value is still there + # modify workflow to jump to another status after the edition + st2 = workflow.add_status('Status2', 'st2') + editable.status = st2.id + workflow.store() + + assert formdef.data_class().get(data_id).status == 'wf-%s' % st1.id + page = login(get_app(pub), username='foo', password='foo').get('/test/%s/' % data_id) + assert 'button_editable-button' in page.body + assert 'barXYZ' in page.body + + resp = page.forms[0].submit('button_editable') + assert resp.location.startswith('http://example.net/test/%s/wfedit-' % data_id) + resp = resp.follow() + assert resp.forms[0]['f1'].value == 'foo2' + resp.forms[0]['f1'] = 'foo3' + resp = resp.forms[0].submit('submit') + assert resp.forms[0]['f3'].value == 'barXYZ' + resp = resp.forms[0].submit('submit') + assert resp.location == 'http://example.net/test/%s/' % data_id + resp = resp.follow() + assert 'foo3' in resp.body # modified value is there + assert 'barXYZ' in resp.body # unchanged value is still there + assert formdef.data_class().get(data_id).status == 'wf-%s' % st2.id def test_form_count_dispatching(pub): user = create_user(pub) diff --git a/wcs/backoffice/management.py b/wcs/backoffice/management.py index 8f03d18..b7dd7f4 100644 --- a/wcs/backoffice/management.py +++ b/wcs/backoffice/management.py @@ -51,7 +51,6 @@ from wcs.admin.settings import UserFieldsFormDef from wcs.categories import Category from wcs.formdef import FormDef from wcs.roles import logged_users_role -from wcs.workflows import EditableWorkflowStatusItem from .submission import FormFillPage @@ -1580,7 +1579,8 @@ class FormPage(Directory): class FormBackOfficeStatusPage(FormStatusPage): - _q_exports = ['', 'download', 'json', 'wfedit', 'action'] + _q_exports = ['', 'download', 'json', 'action'] + form_page_class = FormFillPage def html_top(self, title = None): return html_top('management', title) @@ -1704,20 +1704,6 @@ class FormBackOfficeStatusPage(FormStatusPage): return r.getvalue() - def wfedit(self): - wf_status = self.filled.get_status() - for item in wf_status.items: - if not isinstance(item, EditableWorkflowStatusItem): - continue - if item.check_auth(self.filled, get_request().user): - f = FormFillPage(self.formdef.url_name) - f.edit_mode = True - get_response().breadcrumb = get_response().breadcrumb[:-1] - get_response().breadcrumb.append( ('wfedit', _('Edit')) ) - return f._q_index(editing=self.filled) - - raise errors.AccessForbiddenError() - class FakeField(object): def __init__(self, id, type_, label): diff --git a/wcs/backoffice/submission.py b/wcs/backoffice/submission.py index d2c1418..5e36135 100644 --- a/wcs/backoffice/submission.py +++ b/wcs/backoffice/submission.py @@ -27,8 +27,6 @@ from wcs.forms.root import FormPage as PublicFormFillPage class FormFillPage(PublicFormFillPage): - edit_mode = False - def html_top(self, *args, **kwargs): return html_top('submission', *args, **kwargs) @@ -64,11 +62,11 @@ class FormFillPage(PublicFormFillPage): return r.getvalue() - def form_side(self, step_no, page_no=0, log_detail=None, data=None, editing=None): + def form_side(self, step_no, page_no=0, log_detail=None, data=None): r = TemplateIO(html=True) get_response().filter['sidebar'] = self.get_sidebar(data) r += htmltext('
') - r += self.step(step_no, page_no, log_detail, data=data, editing=editing) + r += self.step(step_no, page_no, log_detail, data=data) r += htmltext('
') return r.getvalue() diff --git a/wcs/forms/common.py b/wcs/forms/common.py index 23ea5d4..c04412b 100644 --- a/wcs/forms/common.py +++ b/wcs/forms/common.py @@ -24,6 +24,7 @@ from quixote.html import TemplateIO, htmltext from wcs.fields import WidgetField, FileField from wcs import file_validation +from wcs.workflows import EditableWorkflowStatusItem from qommon import template from qommon import get_logger @@ -91,6 +92,7 @@ class FilesDirectory(Directory): class FormStatusPage(Directory): _q_exports = ['', 'download', 'json', 'action'] _q_extra_exports = [] + form_page_class = None def html_top(self, title = None): template.html_top(title = title, default_org = _('Forms')) @@ -586,9 +588,31 @@ class FormStatusPage(Directory): if component == 'files': self.check_receiver() return FilesDirectory(self.filled) + if component.startswith('wfedit-'): + return self.wfedit(component[len('wfedit-'):]) return Directory._q_lookup(self, component) def _q_traverse(self, path): get_response().breadcrumb.append( (str(self.filled.id) + '/',str(self.filled.id))) return super(FormStatusPage, self)._q_traverse(path) + + def wfedit(self, action_id): + wf_status = self.filled.get_status() + for item in wf_status.items: + if item.id != action_id: + continue + if not isinstance(item, EditableWorkflowStatusItem): + break + if not item.check_auth(self.filled, get_request().user): + break + f = self.form_page_class(self.formdef.url_name) + f.edit_mode = True + f.edited_data = self.filled + f.edit_action_id = action_id + f.action_url = 'wfedit-%s' % action_id + get_response().breadcrumb = get_response().breadcrumb[:-1] + get_response().breadcrumb.append((f.action_url, _('Edit'))) + return f._q_index() + + raise errors.AccessForbiddenError() diff --git a/wcs/forms/root.py b/wcs/forms/root.py index db95aed..70bfcd2 100644 --- a/wcs/forms/root.py +++ b/wcs/forms/root.py @@ -48,7 +48,7 @@ from wcs.categories import Category from wcs.formdef import FormDef from wcs.formdata import FormData from wcs.roles import logged_users_role -from wcs.workflows import Workflow, EditableWorkflowStatusItem +from wcs.workflows import Workflow from qommon.admin.texts import TextsDirectory @@ -221,6 +221,8 @@ class FormPage(Directory): self.tokens = TokensDirectory(self.formdef) self.code = TrackingCodesDirectory(self.formdef) + self.action_url = '.' + self.edit_mode = False self.page_number = len([ x for x in self.formdef.fields[1:] if x.type == 'page']) + 1 @@ -255,7 +257,7 @@ class FormPage(Directory): if not user_roles.intersection(other_roles): raise errors.AccessForbiddenError() - def step(self, step_no, page_no = 0, log_detail = None, data = None, editing = None): + def step(self, step_no, page_no=0, log_detail=None, data=None): if step_no == 0: self.substvars['current_page_no'] = str(page_no + 1) if log_detail: @@ -282,7 +284,7 @@ class FormPage(Directory): if step_no > 0: current_position = len(page_labels) + step_no - if self.formdef.confirmation and not editing: + if self.formdef.confirmation and not self.edit_mode: page_labels.append(_('Validating')) r = TemplateIO(html=True) @@ -327,7 +329,7 @@ class FormPage(Directory): return self.page(0) - def page(self, page_no, page_change = True, log_detail = None, editing = None): + def page(self, page_no, page_change=True, log_detail=None): r = TemplateIO(html=True) displayed_fields = [] @@ -344,16 +346,13 @@ class FormPage(Directory): form = self.formdef.create_form(page_no, displayed_fields) if get_request().is_in_backoffice(): form.attrs['data-is-backoffice'] = 'true' - if editing: - form.action = 'wfedit' - else: - form.action = '.' + form.action = self.action_url # include a data-has-draft attribute on the
element when a draft # already exists for the form; this will activate the autosave. magictoken = get_request().form.get('magictoken') if magictoken: form_data = session.get_by_magictoken(magictoken, {}) - if self.formdef.enable_tracking_codes and not editing: + if self.formdef.enable_tracking_codes and not self.edit_mode: form.attrs['data-has-draft'] = 'yes' else: form_data = {} @@ -369,7 +368,7 @@ class FormPage(Directory): cancelurl = get_request().form['cancelurl'] session.add_magictoken(magictoken, {'__cancelurl': cancelurl}) - if editing and page_no == self.page_number - 1: + if self.edit_mode and page_no == self.page_number - 1: form.add_submit('submit', _('Save Changes')) elif not self.formdef.confirmation and page_no == self.page_number - 1: form.add_submit('submit', _('Submit')) @@ -432,32 +431,32 @@ class FormPage(Directory): req.form = {} self.html_top(self.formdef.name) - r += self.form_side(0, page_no, log_detail=log_detail, data=data, editing=editing) + r += self.form_side(0, page_no, log_detail=log_detail, data=data) r += get_session().display_message() form.add_hidden('step', '0') form.add_hidden('page', page_no) form.add_submit('cancel', _('Cancel'), css_class = 'cancel') - if self.formdef.enable_tracking_codes and not editing: + if self.formdef.enable_tracking_codes and not self.edit_mode: form.add_submit('savedraft', _('Save Draft'), css_class = 'save-draft', attrs={'style': 'display: none'}) r += form.render() return r.getvalue() - def form_side(self, step_no, page_no=0, log_detail=None, data=None, editing=None): + def form_side(self, step_no, page_no=0, log_detail=None, data=None): '''Create the elements that typically appear aside the main form (tracking code and steps).''' r = TemplateIO(html=True) r += htmltext('
') if self.formdef.enable_tracking_codes: - r += self.tracking_code_box(data, editing=editing) - r += self.step(step_no, page_no, log_detail, data=data, editing=editing) + r += self.tracking_code_box(data) + r += self.step(step_no, page_no, log_detail, data=data) r += htmltext('
') return r.getvalue() - def tracking_code_box(self, data, editing=False): + def tracking_code_box(self, data): '''Create the tracking code box, it displays the current tracking code or a 'save' button if it has not yet been created.''' r = TemplateIO(html=True) @@ -477,7 +476,7 @@ class FormPage(Directory): r += htmltext('%s') % ( 'code/%s/' % tracking_code, tracking_code) else: - if editing: + if self.edit_mode: return '' r += htmltext('') % _('Save') r += TextsDirectory.get_html_text('tracking-code-short-text') @@ -507,7 +506,7 @@ class FormPage(Directory): raise errors.AccessForbiddenError() return False - def _q_index(self, log_detail = None, editing = None): + def _q_index(self, log_detail=None): self.check_role() if self.check_disabled(): return redirect(self.check_disabled()) @@ -533,7 +532,7 @@ class FormPage(Directory): # first hit on first page, if tracking code are enabled and we # are not editing an existing formdata, generate a new tracking # code. - if not editing and self.formdef.enable_tracking_codes and not get_request().form.has_key('mt'): + if not self.edit_mode and self.formdef.enable_tracking_codes and not get_request().form.has_key('mt'): tracking_code = get_publisher().tracking_code_class() tracking_code.store() token = randbytes(8) @@ -541,16 +540,16 @@ class FormPage(Directory): session.add_magictoken(token, {'future_tracking_code': tracking_code.id}) existing_formdata = None - if editing: - existing_formdata = editing.data + if self.edit_mode: + existing_formdata = self.edited_data.data if not get_request().form: # on the initial visit editing the form (i.e. not after # clicking for previous or next page), we need to load the # existing data into the session - editing.feed_session() + self.edited_data.feed_session() token = randbytes(8) get_request().form['magictoken'] = token - session.add_magictoken(token, editing.data) + session.add_magictoken(token, self.edited_data.data) elif self.formdef.only_allow_one: user_forms = get_user_forms(self.formdef) if [x for x in user_forms if not x.is_draft()]: @@ -593,11 +592,11 @@ class FormPage(Directory): self.feed_current_data(magictoken) return self.page(page_no, True) self.feed_current_data(None) - return self.page(0, editing = editing) + return self.page(0) if form.get_submit() == 'cancel': get_logger().info('form %s - cancel' % (self.formdef.name)) - if editing: + if self.edit_mode: return redirect('.') try: magictoken = form.get_widget('magictoken').parse() @@ -634,7 +633,7 @@ class FormPage(Directory): form.add_submit('savedraft') form.add_submit('submit') if page_no > 0 and form.get_submit() == 'previous': - return self.previous_page(page_no, magictoken, editing = editing) + return self.previous_page(page_no, magictoken) if self.formdef.enable_tracking_codes and form.get_submit() == 'removedraft': self.remove_draft() @@ -657,7 +656,7 @@ class FormPage(Directory): # page hidden field had an error in its submission), in # that case we just fall back to the first page. page_no = 0 - return self.page(page_no, page_change = False, editing = editing) + return self.page(page_no, page_change=False) form_data = session.get_by_magictoken(magictoken, {}) data = self.formdef.get_data(form) @@ -693,9 +692,9 @@ class FormPage(Directory): if field.convert_value_to_str: v = field.convert_value_to_str(v) req.form['f%s' % k] = v - if editing: - form = self.formdef.create_view_form(form_data, use_tokens = False) - return self.submitted_existing(form, editing) + if self.edit_mode: + form = self.formdef.create_view_form(form_data, use_tokens=False) + return self.submitted_existing(form) if self.formdef.confirmation: return self.validating(form_data) else: @@ -711,14 +710,14 @@ class FormPage(Directory): form.add_submit('savedraft') else: - return self.page(page_no, editing = editing) + return self.page(page_no) if step == 1: form.add_submit('previous') magictoken = form.get_widget('magictoken').parse() if form.get_submit() == 'previous': - return self.previous_page(self.page_number, magictoken, editing = editing) + return self.previous_page(self.page_number, magictoken) magictoken = form.get_widget('magictoken').parse() form_data = session.get_by_magictoken(magictoken, {}) data = self.formdef.get_data(form) @@ -739,7 +738,7 @@ class FormPage(Directory): form_data = session.get_by_magictoken(magictoken, {}) if form.get_submit() == 'previous': - return self.previous_page(self.page_number, magictoken, editing = editing) + return self.previous_page(self.page_number, magictoken) if self.formdef.enable_tracking_codes and form.get_submit() == 'removedraft': self.remove_draft() @@ -761,7 +760,7 @@ class FormPage(Directory): return self.submitted(form, existing_formdata) - def previous_page(self, page_no, magictoken, editing = None): + def previous_page(self, page_no, magictoken): session = get_session() form_data = session.get_by_magictoken(magictoken, {}) @@ -777,7 +776,7 @@ class FormPage(Directory): if previous_page.is_visible(form_data, self.formdef): break - return self.page(page_no, page_change = True, editing = editing) + return self.page(page_no, page_change=True) def remove_draft(self): magictoken = get_request().form.get('magictoken') @@ -930,11 +929,17 @@ class FormPage(Directory): code.id = magictoken_data['future_tracking_code'] code.formdata = formdata # this will .store() the code - def submitted_existing(self, form, editing): - old_data = editing.data - editing.data = self.formdef.get_data(form) - editing.store() - return redirect('.') + def submitted_existing(self, form): + self.edited_data.data = self.formdef.get_data(form) + self.edited_data.store() + wf_status = self.edited_data.get_status() + url = None + for item in wf_status.items: + if item.id == self.edit_action_id and item.status: + self.edited_data.jump_status(item.status) + url = self.edited_data.perform_workflow() + break + return redirect(url or '.') def tempfile(self): self.check_role() @@ -1386,7 +1391,8 @@ class RootDirectory(AccessControlled, Directory): class PublicFormStatusPage(FormStatusPage): - _q_exports = ['', 'download', 'status', 'wfedit'] + _q_exports = ['', 'download', 'status'] + form_page_class = FormPage def __init__(self, *args, **kwargs): FormStatusPage.__init__(self, *args, **kwargs) @@ -1409,19 +1415,6 @@ class PublicFormStatusPage(FormStatusPage): r += htmltext('') return r.getvalue() - def wfedit(self): - wf_status = self.filled.get_status() - for item in wf_status.items: - if not isinstance(item, EditableWorkflowStatusItem): - continue - if item.check_auth(self.filled, get_request().user): - f = FormPage(self.formdef.url_name) - get_response().breadcrumb = get_response().breadcrumb[:-1] - get_response().breadcrumb.append( ('wfedit', _('Edit')) ) - return f._q_index(editing = self.filled) - - raise errors.AccessForbiddenError() - TextsDirectory.register('welcome-logged', N_('Welcome text on home page for logged users')) diff --git a/wcs/workflows.py b/wcs/workflows.py index d489601..201fe19 100644 --- a/wcs/workflows.py +++ b/wcs/workflows.py @@ -1862,7 +1862,7 @@ class EditableWorkflowStatusItem(WorkflowStatusItem): def submit_form(self, form, formdata, user, evo): if form.get_submit() == 'button%s' % self.id: - return formdata.get_url(backoffice=get_request().is_in_backoffice()) + 'wfedit' + return formdata.get_url(backoffice=get_request().is_in_backoffice()) + 'wfedit-%s' % self.id def add_parameters_widgets(self, form, parameters, prefix='', formdef=None): if 'by' in parameters: -- 2.6.4