From 611fc7e8ce83a7dfd26a41d66d66fb040a70b205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Mon, 6 Jun 2016 18:55:21 +0200 Subject: [PATCH 1/5] general: add support for backoffice fields (#8273) --- tests/test_admin_pages.py | 49 +++++++++++++++++++++++++++++++ wcs/admin/workflows.py | 59 +++++++++++++++++++++++++++++++++++++ wcs/backoffice/management.py | 14 ++++----- wcs/formdef.py | 10 ++++++- wcs/sql.py | 16 +++++----- wcs/workflows.py | 70 +++++++++++++++++++++++++++++++++++++++----- 6 files changed, 195 insertions(+), 23 deletions(-) diff --git a/tests/test_admin_pages.py b/tests/test_admin_pages.py index 9abeb00..41087e3 100644 --- a/tests/test_admin_pages.py +++ b/tests/test_admin_pages.py @@ -1556,6 +1556,55 @@ def test_workflows_variables_edit(pub): assert Workflow.get(1).variables_formdef.fields[0].key == 'string' assert Workflow.get(1).variables_formdef.fields[0].varname == '1*1*message' +def test_workflows_backoffice_fields(pub): + create_superuser(pub) + create_role() + + Workflow.wipe() + workflow = Workflow(name='foo') + workflow.store() + + formdef = FormDef() + formdef.name = 'form title' + formdef.workflow_id = workflow.id + formdef.fields = [] + formdef.store() + + app = login(get_app(pub)) + resp = app.get('/backoffice/workflows/1/') + resp = resp.click(href='backoffice-fields/') + assert resp.location == 'http://example.net/backoffice/workflows/1/backoffice-fields/fields/' + resp = resp.follow() + + # makes sure we can't add page fields + assert 'value="New Page"' not in resp.body + + # add a simple field + resp.forms[0]['label'] = 'foobar' + resp.forms[0]['type'] = 'Text (line)' + resp = resp.forms[0].submit() + assert resp.location == 'http://example.net/backoffice/workflows/1/backoffice-fields/fields/' + resp = resp.follow() + + # check it's been saved correctly + assert 'foobar' in resp.body + assert len(Workflow.get(1).backoffice_fields_formdef.fields) == 1 + assert Workflow.get(1).backoffice_fields_formdef.fields[0].id.startswith('bo') + assert Workflow.get(1).backoffice_fields_formdef.fields[0].key == 'string' + assert Workflow.get(1).backoffice_fields_formdef.fields[0].label == 'foobar' + + backoffice_field_id = Workflow.get(1).backoffice_fields_formdef.fields[0].id + formdef = FormDef.get(formdef.id) + data_class = formdef.data_class() + data_class.wipe() + formdata = data_class() + formdata.data = {backoffice_field_id: 'HELLO'} + formdata.status = 'wf-new' + formdata.store() + + assert data_class.get(formdata.id).data[backoffice_field_id] == 'HELLO' + + def test_workflows_functions(pub): create_superuser(pub) create_role() diff --git a/wcs/admin/workflows.py b/wcs/admin/workflows.py index 26c671b..afcd08a 100644 --- a/wcs/admin/workflows.py +++ b/wcs/admin/workflows.py @@ -832,6 +832,10 @@ class WorkflowVariablesFieldDefPage(FieldDefPage): return form +class WorkflowBackofficeFieldDefPage(FieldDefPage): + section = 'workflows' + + class WorkflowVariablesFieldsDirectory(FieldsDirectory): _q_exports = ['', 'update_order', 'new'] @@ -853,6 +857,27 @@ class WorkflowVariablesFieldsDirectory(FieldsDirectory): pass +class WorkflowBackofficeFieldsDirectory(FieldsDirectory): + _q_exports = ['', 'update_order', 'new'] + + section = 'workflows' + field_def_page_class = WorkflowBackofficeFieldDefPage + support_import = False + blacklisted_types = ['page'] + + def index_top(self): + r = TemplateIO(html=True) + r += htmltext('

%s - %s - %s

') % (_('Workflow'), + self.objectdef.name, _('Backoffice Fields')) + r += get_session().display_message() + if not self.objectdef.fields: + r += htmltext('

%s

') % _('There are not yet any backoffice fields.') + return r.getvalue() + + def index_bottom(self): + pass + + class VariablesDirectory(Directory): _q_exports = ['', 'fields'] @@ -869,6 +894,23 @@ class VariablesDirectory(Directory): return Directory._q_traverse(self, path) +class BackofficeFieldsDirectory(Directory): + _q_exports = ['', 'fields'] + + def __init__(self, workflow): + self.workflow = workflow + + def _q_index(self): + return redirect('fields/') + + def _q_traverse(self, path): + get_response().breadcrumb.append(('backoffice-fields/', _('Backoffice Fields'))) + self.fields = WorkflowBackofficeFieldsDirectory( + WorkflowBackofficeFieldsFormDef(self.workflow)) + return Directory._q_traverse(self, path) + + + class FunctionsDirectory(Directory): _q_exports = ['', 'new'] @@ -1266,6 +1308,7 @@ class GlobalActionsDirectory(Directory): class WorkflowPage(Directory): _q_exports = ['', 'edit', 'delete', 'newstatus', ('status', 'status_dir'), 'update_order', 'duplicate', 'export', 'svg', ('variables', 'variables_dir'), + ('backoffice-fields', 'backoffice_fields_dir'), 'update_actions_order', 'update_criticality_levels_order', ('functions', 'functions_dir'), ('global-actions', 'global_actions_dir'), ('criticality-levels', 'criticality_levels_dir'), @@ -1280,6 +1323,7 @@ class WorkflowPage(Directory): self.workflow_ui = WorkflowUI(self.workflow) self.status_dir = WorkflowStatusDirectory(self.workflow, html_top) self.variables_dir = VariablesDirectory(self.workflow) + self.backoffice_fields_dir = BackofficeFieldsDirectory(self.workflow) self.functions_dir = FunctionsDirectory(self.workflow) self.global_actions_dir = GlobalActionsDirectory(self.workflow, html_top) self.criticality_levels_dir = CriticalityLevelsDirectory(self.workflow) @@ -1425,6 +1469,21 @@ class WorkflowPage(Directory): r += htmltext('') r += htmltext('') + if not str(self.workflow.id).startswith('_'): + r += htmltext('
') + r += htmltext('

%s') % _('Backoffice Fields') + r += htmltext(' (%s)

') % _('change') + if self.workflow.backoffice_fields_formdef: + r += htmltext('') + r += htmltext('
') + r += htmltext('') # .splitcontent-right r += htmltext('
') diff --git a/wcs/backoffice/management.py b/wcs/backoffice/management.py index 27c7b5b..7a9fb41 100644 --- a/wcs/backoffice/management.py +++ b/wcs/backoffice/management.py @@ -183,7 +183,7 @@ class UserViewDirectory(Directory): r += htmltext('

%s

') % self.user.display_name formdef = UserFieldsFormDef() r += htmltext('
') - for field in formdef.fields: + for field in formdef.get_fields(): if not hasattr(field, str('get_view_value')): continue value = self.user.form_data.get(field.id) @@ -321,7 +321,7 @@ class UsersViewDirectory(Directory): formdef = UserFieldsFormDef() criteria_fields = [ILike('name', query), ILike('email', query)] - for field in formdef.fields: + for field in formdef.get_fields(): if field.type in ('string', 'text', 'email'): criteria_fields.append(ILike('f%s' % field.id, query)) if get_publisher().is_using_postgresql(): @@ -342,7 +342,7 @@ class UsersViewDirectory(Directory): r += htmltext('%s') % _('Name') if include_email_column: r += htmltext('%s') % _('Email') - for field in formdef.fields: + for field in formdef.get_fields(): if field.in_listing: r += htmltext('%s') % ( field.id, field.label) @@ -356,7 +356,7 @@ class UsersViewDirectory(Directory): r += htmltext('%s') % (user.name or '') if include_email_column: r += htmltext('%s') % (user.email or '') - for field in formdef.fields: + for field in formdef.get_fields(): if field.in_listing: r += htmltext('%s') % (user.form_data.get(field.id) or '') r += htmltext('') @@ -1036,7 +1036,7 @@ class FormPage(Directory): fields.append(FakeField('submission_channel', 'submission_channel', _('Channel'))) fields.append(FakeField('time', 'time', _('Time'))) fields.append(FakeField('user-label', 'user-label', _('User Label'))) - fields.extend(self.formdef.fields) + fields.extend(self.formdef.get_fields()) fields.append(FakeField('status', 'status', _('Status'))) fields.append(FakeField('anonymised', 'anonymised', _('Anonymised'))) @@ -1046,7 +1046,7 @@ class FormPage(Directory): field_ids = [x for x in get_request().form.keys()] if not field_ids or ignore_form: field_ids = ['id', 'time', 'user-label'] - for field in self.formdef.fields: + for field in self.formdef.get_fields(): if hasattr(field, str('get_view_value')) and field.in_listing: field_ids.append(field.id) field_ids.append('status') @@ -1636,7 +1636,7 @@ class FormPage(Directory): had_page = False last_page = None last_title = None - for f in self.formdef.fields: + for f in self.formdef.get_fields(): if excluded_fields and f.id in excluded_fields: continue if f.type == 'page': diff --git a/wcs/formdef.py b/wcs/formdef.py index c44c39e..262b612 100644 --- a/wcs/formdef.py +++ b/wcs/formdef.py @@ -292,7 +292,15 @@ class FormDef(StorableObject): rebuild_global_views=True) return t - def rebuild_views(self): + def get_fields(self): + workflow_fields = [] + try: + workflow_fields = self.workflow.backoffice_fields_formdef.fields + except AttributeError: + pass + return (self.fields or []) + workflow_fields + + def rebuild(self): if get_publisher().is_using_postgresql(): import sql sql.do_formdef_tables(self, rebuild_views=True, diff --git a/wcs/sql.py b/wcs/sql.py index 7bb068d..7f0b5de 100644 --- a/wcs/sql.py +++ b/wcs/sql.py @@ -378,7 +378,7 @@ def do_formdef_tables(formdef, conn=None, cur=None, rebuild_views=False, rebuild cur.execute('''ALTER TABLE %s ADD COLUMN criticality_level integer NOT NULL DEFAULT(0)''' % table_name) # add new fields - for field in formdef.fields: + for field in formdef.get_fields(): assert field.id is not None sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar') if sql_type is None: @@ -461,7 +461,7 @@ def do_user_table(): from admin.settings import UserFieldsFormDef formdef = UserFieldsFormDef() - for field in formdef.fields: + for field in formdef.get_fields(): sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar') if sql_type is None: continue @@ -602,7 +602,7 @@ def do_views(formdef, conn, cur, rebuild_global_views=True): view_fields = get_view_fields(formdef) column_names = {} - for field in formdef.fields: + for field in formdef.get_fields(): field_key = 'f%s' % field.id if field.type in ('page', 'title', 'subtitle', 'comment'): continue @@ -900,7 +900,7 @@ class SqlMixin(object): def get_sql_dict_from_data(self, data, formdef): sql_dict = {} - for field in formdef.fields: + for field in formdef.get_fields(): sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar') if sql_type is None: continue @@ -932,7 +932,7 @@ class SqlMixin(object): i = len(cls._table_static_fields) if formdef.geolocations: i += len(formdef.geolocations.keys()) - for field in formdef.fields: + for field in formdef.get_fields(): sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar') if sql_type is None: continue @@ -1220,7 +1220,7 @@ class SqlFormData(SqlMixin, wcs.formdata.FormData): fts_strings = [str(self.id)] if self.tracking_code: fts_strings.append(self.tracking_code) - for field in self._formdef.fields: + for field in self._formdef.get_fields(): if not self.data.get(field.id): continue value = None @@ -1275,7 +1275,7 @@ class SqlFormData(SqlMixin, wcs.formdata.FormData): @classmethod def get_data_fields(cls): data_fields = ['geoloc_%s' % x for x in (cls._formdef.geolocations or {}).keys()] - for field in cls._formdef.fields: + for field in cls._formdef.get_fields(): sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar') if sql_type is None: continue @@ -1492,7 +1492,7 @@ class SqlUser(SqlMixin, wcs.users.User): @classmethod def get_data_fields(cls): data_fields = [] - for field in cls.get_formdef().fields: + for field in cls.get_formdef().get_fields(): sql_type = SQL_TYPE_MAPPING.get(field.key, 'varchar') if sql_type is None: continue diff --git a/wcs/workflows.py b/wcs/workflows.py index e7d6496..741cb72 100644 --- a/wcs/workflows.py +++ b/wcs/workflows.py @@ -246,12 +246,40 @@ class WorkflowVariablesFieldsFormDef(FormDef): self.workflow.store() +class WorkflowBackofficeFieldsFormDef(FormDef): + '''Class to handle workflow backoffice fields, it loads and saves from/to + the workflow object 'backoffice_fields_formdef' attribute.''' + + def __init__(self, workflow): + self.id = None + self.name = workflow.name + self.workflow = workflow + if workflow.backoffice_fields_formdef and workflow.backoffice_fields_formdef.fields: + self.fields = self.workflow.backoffice_fields_formdef.fields + self.max_field_id = max([lax_int(x.id) for x in self.fields or []]) + else: + self.fields = [] + + def get_new_field_id(self): + if self.max_field_id is None: + field_id = 1 + else: + field_id = self.max_field_id + 1 + self.max_field_id = field_id + return 'bo%s' % field_id + + def store(self): + self.workflow.backoffice_fields_formdef = self + self.workflow.store() + + class Workflow(StorableObject): _names = 'workflows' name = None possible_status = None roles = None variables_formdef = None + backoffice_fields_formdef = None global_actions = None criticality_levels = None @@ -283,16 +311,28 @@ class Workflow(StorableObject): self.store() def store(self): - must_update_views = False + must_update = False if self.id: old_self = self.get(self.id, ignore_errors=True, ignore_migration=True) if old_self: old_endpoints = set([x.id for x in old_self.get_endpoint_status()]) if old_endpoints != set([x.id for x in self.get_endpoint_status()]): - must_update_views = True + must_update = True old_criticality_levels = len(old_self.criticality_levels or [0]) if old_criticality_levels != len(self.criticality_levels or [0]): - must_update_views = True + must_update = True + try: + old_backoffice_fields = old_self.backoffice_fields_formdef.fields + except AttributeError: + old_backoffice_fields = [] + try: + new_backoffice_fields = self.backoffice_fields_formdef.fields + except AttributeError: + new_backoffice_fields = [] + if len(old_backoffice_fields) != len(new_backoffice_fields): + must_update = True + elif self.backoffice_fields_formdef: + must_update = True self.last_modification_time = time.localtime() if get_request() and get_request().user: @@ -301,12 +341,11 @@ class Workflow(StorableObject): self.last_modification_user_id = None StorableObject.store(self) - # instruct all related formdefs to update their security rules, and - # their views if endpoints have changed. + # instruct all related formdefs to update. for form in FormDef.select(lambda x: x.workflow_id == self.id, ignore_migration=True): form.data_class().rebuild_security() - if must_update_views: - form.rebuild_views() + if must_update: + form.rebuild() @classmethod def get(cls, id, ignore_errors=False, ignore_migration=False): @@ -472,6 +511,14 @@ class Workflow(StorableObject): for field in self.variables_formdef.fields: fields.append(field.export_to_xml(charset=charset, include_id=include_id)) + if self.backoffice_fields_formdef: + variables = ET.SubElement(root, 'backoffice-fields') + formdef = ET.SubElement(variables, 'formdef') + ET.SubElement(formdef, 'name').text = '-' # required by formdef xml import + fields = ET.SubElement(formdef, 'fields') + for field in self.backoffice_fields_formdef.fields: + fields.append(field.export_to_xml(charset=charset, include_id=include_id)) + return root @classmethod @@ -543,6 +590,14 @@ class Workflow(StorableObject): imported_formdef = FormDef.import_from_xml_tree(formdef, include_id=True) workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow) workflow.variables_formdef.fields = imported_formdef.fields + + variables = tree.find('backoffice-fields') + if variables is not None: + formdef = variables.find('backoffice-fields') + imported_formdef = FormDef.import_from_xml_tree(formdef, include_id=True) + workflow.backoffice_fields_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow) + workflow.backoffice_fields_formdef.fields = imported_formdef.fields + return workflow def get_list_of_roles(self, include_logged_in_users=True): @@ -2229,5 +2284,6 @@ def load_extra(): import wf.resubmit import wf.criticality import wf.profile + import wf.set_backoffice_variable from wf.export_to_model import ExportToModel -- 2.8.1