From 81d5c6ae3893eae70301d5e2776a8ae4b79a44c1 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Sat, 12 Dec 2020 10:49:22 +0100
Subject: [PATCH 1/3] formdata: hold get_role_translation function (#38254)

---
 wcs/backoffice/management.py |  4 ++--
 wcs/formdata.py              | 24 ++++++++++++++++++------
 wcs/forms/workflows.py       |  3 +--
 wcs/wf/aggregation_email.py  |  5 ++---
 wcs/wf/notification.py       |  4 ++--
 wcs/workflows.py             | 28 ++++++----------------------
 6 files changed, 31 insertions(+), 37 deletions(-)

diff --git a/wcs/backoffice/management.py b/wcs/backoffice/management.py
index 84579a6a..3777e5a8 100644
--- a/wcs/backoffice/management.py
+++ b/wcs/backoffice/management.py
@@ -59,17 +59,17 @@ from wcs.conditions import Condition
 from wcs.forms.backoffice import FormDefUI
 from wcs.forms.common import FormStatusPage
 from wcs.admin.settings import UserFieldsFormDef
 from wcs.categories import Category
 from wcs.formdata import FormData
 from wcs.formdef import FormDef
 from wcs.roles import logged_users_role, Role
 from wcs.variables import LazyFieldVar
-from wcs.workflows import get_role_translation, template_on_formdata
+from wcs.workflows import template_on_formdata
 
 from .submission import FormFillPage
 
 
 def geojson_formdatas(formdatas, geoloc_key='base', fields=None):
     geojson = {
         'type': 'FeatureCollection',
         'features': []
@@ -3180,17 +3180,17 @@ class FormBackOfficeStatusPage(FormStatusPage):
         # assigned functions
         if self.formdef.workflow.roles:
             workflow = self.formdef.workflow
             r += htmltext('<div id="inspect-functions" class="section">')
             r += htmltext('<h2>%s</h2></li>\n') % _('Functions')
             r += htmltext('<ul class="form-inspector biglist">')
             for key, label in (workflow.roles or {}).items():
                 r += htmltext('<li><span class="label">%s</span>') % label
-                acting_role_id = get_role_translation(self.filled, key)
+                acting_role_id = self.filled.get_role_translation(key)
                 if acting_role_id:
                     try:
                         acting_role = Role.get(acting_role_id)
                         r += htmltext('<div class="value"><span>%s</span></div>') % acting_role.name
                     except KeyError:
                         r += htmltext('<div class="value"><span>%s %s</span></div>') % (
                                 acting_role_id, _('(deleted)'))
                 else:
diff --git a/wcs/formdata.py b/wcs/formdata.py
index 722974f7..5ec45337 100644
--- a/wcs/formdata.py
+++ b/wcs/formdata.py
@@ -631,21 +631,35 @@ class FormData(StorableObject):
         return self.get_url(backoffice=True)
 
     def get_api_url(self):
         return '%s%s/' % (self.formdef.get_api_url(), self.id)
 
     def get_display_id(self):
         return str(self.id_display or self.id)
 
+    def get_role_translation(self, role_name):
+        if role_name == '_submitter':
+            raise Exception('_submitter is not a valid role')
+        elif str(role_name).startswith('_'):
+            role_id = None
+            if self.workflow_roles:
+                role_id = self.workflow_roles.get(role_name)
+            if not role_id and self.formdef.workflow_roles:
+                role_id = self.formdef.workflow_roles.get(role_name)
+            if role_id is None:
+                return role_id
+            return str(role_id)
+        else:
+            return str(role_name)
+
     def get_handling_role_id(self):
         # TODO: look at current status and return the role(s) actually
         # concerned by the handling of the formdata
-        from wcs.workflows import get_role_translation
-        return get_role_translation(self, '_receiver')
+        return self.get_role_translation('_receiver')
 
     def get_handling_role(self):
         try:
             return Role.get(self.get_handling_role_id())
         except KeyError:
             return None
 
     def get_field_view_value(self, field, max_length=None):
@@ -925,19 +939,18 @@ class FormData(StorableObject):
             # drafts are only visible to submitter
             return ['_submitter']
 
         status_action_roles = set()
 
         # make sure the handling roles always gets access to the formdata, till
         # the very end (where it may be that there is no workflow status item
         # at all).
-        from wcs.workflows import get_role_translation
         for function_key in self.formdef.workflow.roles.keys():
-            handling_role = get_role_translation(self, function_key)
+            handling_role = self.get_role_translation(function_key)
             if handling_role:
                 status_action_roles.add(handling_role)
 
         wf_status = self.get_status()
         if not wf_status:
             status_action_roles.add('_submitter')
         else:
             status_action_roles |= set(self.get_actions_roles())
@@ -948,29 +961,28 @@ class FormData(StorableObject):
     def get_actions_roles(self):
         if self.is_draft():
             return []
 
         wf_status = self.get_status()
         if not wf_status:
             return []
 
-        from wcs.workflows import get_role_translation
         status_action_roles = set()
         for item in wf_status.items or []:
             if not hasattr(item, 'by') or not item.by:
                 continue
             with get_publisher().substitutions.temporary_feed(self):
                 if not item.check_condition(self):
                     continue
             for role in item.by:
                 if role == '_submitter':
                     status_action_roles.add(role)
                 else:
-                    real_role = get_role_translation(self, role)
+                    real_role = self.get_role_translation(role)
                     if real_role:
                         status_action_roles.add(real_role)
 
         return status_action_roles
 
     actions_roles = property(get_actions_roles)
 
     def get_last_update_time(self):
diff --git a/wcs/forms/workflows.py b/wcs/forms/workflows.py
index 78371f82..47b479bf 100644
--- a/wcs/forms/workflows.py
+++ b/wcs/forms/workflows.py
@@ -20,17 +20,16 @@ from quixote import get_request, get_response
 from quixote.directory import Directory
 
 from ..qommon import errors
 
 from wcs.api import get_user_from_api_query_string, is_url_signed
 from wcs.roles import logged_users_role
 from wcs.workflows import (
         WorkflowGlobalActionWebserviceTrigger,
-        get_role_translation,
         perform_items)
 
 
 class HookDirectory(Directory):
     _q_exports = ['']
 
     def __init__(self, formdata, action, trigger):
         self.formdata = formdata
@@ -47,17 +46,17 @@ class HookDirectory(Directory):
         if self.trigger.roles:
             for role in self.trigger.roles:
                 if role == logged_users_role().id and (user or is_url_signed()):
                     break
                 if role == '_submitter' and self.formdata.is_submitter(user):
                     break
                 if not user:
                     continue
-                if get_role_translation(self.formdata, role) in user.get_roles():
+                if self.formdata.get_role_translation(role) in user.get_roles():
                     break
             else:
                 raise errors.AccessForbiddenError('insufficient roles')
 
         if hasattr(get_request(), '_json'):
             workflow_data = {self.trigger.identifier: get_request().json}
             self.formdata.update_workflow_data(workflow_data)
             self.formdata.store()
diff --git a/wcs/wf/aggregation_email.py b/wcs/wf/aggregation_email.py
index 2d48c185..11ff4dde 100644
--- a/wcs/wf/aggregation_email.py
+++ b/wcs/wf/aggregation_email.py
@@ -16,18 +16,17 @@
 
 from ..qommon import _, N_
 from ..qommon.publisher import get_publisher_class
 from ..qommon.storage import StorableObject
 from ..qommon.form import *
 from ..qommon.cron import CronJob
 from ..qommon import emails
 
-from wcs.workflows import WorkflowStatusItem, register_item_class, \
-        get_role_translation
+from wcs.workflows import WorkflowStatusItem, register_item_class
 
 from wcs.roles import Role
 
 
 class AggregationEmailWorkflowStatusItem(WorkflowStatusItem):
     description = N_('Daily Summary Email')
     key = 'aggregationemail'
     category = 'interaction'
@@ -55,17 +54,17 @@ class AggregationEmailWorkflowStatusItem(WorkflowStatusItem):
     def get_parameters(self):
         return ('to', 'condition')
 
     def perform(self, formdata):
         if not self.to:
             return
 
         for dest in self.to:
-            dest = get_role_translation(formdata, dest)
+            dest = formdata.get_role_translation(dest)
             try:
                 aggregate = AggregationEmail.get(dest)
             except KeyError:
                 aggregate = AggregationEmail(id = dest)
 
             aggregate.append({
                     'formdef': formdata.formdef.id,
                     'formdata': formdata.id,
diff --git a/wcs/wf/notification.py b/wcs/wf/notification.py
index 21465916..8334945b 100644
--- a/wcs/wf/notification.py
+++ b/wcs/wf/notification.py
@@ -20,17 +20,17 @@ from quixote import get_publisher
 
 from ..qommon import _, N_, ezt
 from ..qommon.form import *
 from ..qommon.template import TemplateError
 from ..qommon import get_logger
 
 from wcs.roles import Role
 from wcs.workflows import (WorkflowStatusItem, register_item_class,
-        template_on_formdata, get_role_translation)
+        template_on_formdata)
 from .wscall import WebserviceCallStatusItem
 
 
 class SendNotificationWorkflowStatusItem(WebserviceCallStatusItem):
     description = N_('User Notification')
     key = 'notification'
     category = 'interaction'
     support_substitution_variables = True
@@ -130,17 +130,17 @@ class SendNotificationWorkflowStatusItem(WebserviceCallStatusItem):
         self.url = self.get_api_url()
 
         users = []
         for dest in self.to:
             if dest == '_submitter':
                 users.append(formdata.get_user())
                 continue
 
-            dest = get_role_translation(formdata, dest)
+            dest = formdata.get_role_translation(dest)
             try:
                 role = Role.get(dest)
             except KeyError:
                 continue
             users.extend(get_publisher().user_class.get_users_with_role(role.id))
 
         for user in users:
             if not user or not user.is_active:
diff --git a/wcs/workflows.py b/wcs/workflows.py
index 6fb0ef04..7d440395 100644
--- a/wcs/workflows.py
+++ b/wcs/workflows.py
@@ -500,17 +500,17 @@ class Workflow(StorableObject):
             return []
         actions = []
         for action in self.global_actions or []:
             for trigger in action.triggers or []:
                 if isinstance(trigger, WorkflowGlobalActionManualTrigger):
                     if '_submitter' in (trigger.roles or []) and formdata.is_submitter(user):
                         actions.append(action)
                         break
-                    roles = [get_role_translation(formdata, x)
+                    roles = [formdata.get_role_translation(x)
                              for x in (trigger.roles or []) if x != '_submitter']
                     if set(roles).intersection(user.get_roles()):
                         actions.append(action)
                         break
         return actions
 
     def get_subdirectories(self, formdata):
         wf_status = formdata.get_status()
@@ -1544,17 +1544,17 @@ class WorkflowStatus(object):
                         break
                     if role == '_submitter':
                         if filled.is_submitter(user):
                             break
                         else:
                             continue
                     if user is None:
                         continue
-                    role = get_role_translation(filled, role)
+                    role = filled.get_role_translation(role)
                     if role in user.get_roles():
                         break
                 else:
                     continue
             if not item.check_condition(filled):
                 continue
             yield item
 
@@ -1636,17 +1636,17 @@ class WorkflowStatus(object):
         visibility_roles = self.visibility[:]
         for item in self.items or []:
             if not hasattr(item, 'by') or not item.by:
                 continue
             visibility_roles.extend(item.by)
 
         for role in visibility_roles:
             if role != '_submitter':
-                role = get_role_translation(formdata, role)
+                role = formdata.get_role_translation(role)
             if role in user_roles:
                 return True
         return False
 
     def is_endpoint(self):
         # an endpoint status is a status that marks the end of the workflow; it
         # can either be computed automatically (if there's no way out of the
         # status) or be set manually (to mark the expected end while still
@@ -1836,17 +1836,17 @@ class WorkflowStatusItem(XmlSerialisable):
                 return True
             if role == '_submitter':
                 t = formdata.is_submitter(user)
                 if t is True:
                     return True
                 continue
             if not user:
                 continue
-            role = get_role_translation(formdata, role)
+            role = formdata.get_role_translation(role)
             if role in user.get_roles():
                 return True
 
         return False
 
     def check_condition(self, formdata):
         context = {'formdata': formdata, 'status_item': self}
         try:
@@ -2241,32 +2241,16 @@ class WorkflowStatusJumpItem(WorkflowStatusItem):
                 markers_stack = []
             markers_stack.append({'status_id': formdata.status[3:]})
             formdata.update_workflow_data({'_markers_stack': markers_stack})
 
     def get_parameters(self):
         return ('status', 'set_marker_on_status', 'condition')
 
 
-def get_role_translation(formdata, role_name):
-    if role_name == '_submitter':
-        raise Exception('_submitter is not a valid role')
-    elif str(role_name).startswith('_'):
-        role_id = None
-        if formdata.workflow_roles:
-            role_id = formdata.workflow_roles.get(role_name)
-        if not role_id and formdata.formdef.workflow_roles:
-            role_id = formdata.formdef.workflow_roles.get(role_name)
-        if role_id is None:
-            return role_id
-        return str(role_id)
-    else:
-        return str(role_name)
-
-
 def get_role_translation_label(workflow, role_id):
     if role_id == logged_users_role().id:
         return logged_users_role().name
     if role_id == '_submitter':
         return C_('role|User')
     if str(role_id).startswith('_'):
         return workflow.roles.get(role_id)
     else:
@@ -2733,17 +2717,17 @@ class SendmailWorkflowStatusItem(WorkflowStatusItem):
                 continue
 
             if dest == '_submitter':
                 submitter_email = formdata.formdef.get_submitter_email(formdata)
                 if submitter_email:
                     addresses.append(submitter_email)
                 continue
 
-            dest = get_role_translation(formdata, dest)
+            dest = formdata.get_role_translation(dest)
 
             try:
                 role = Role.get(dest)
             except KeyError:
                 continue
             addresses.extend(role.get_emails())
 
         if not addresses:
@@ -2907,17 +2891,17 @@ class DisplayMessageWorkflowStatusItem(WorkflowStatusItem):
         if not get_request():
             return False
         user = get_request().user
         for role in self.to or []:
             if role == '_submitter':
                 if filled.is_submitter(user):
                     return True
             elif user:
-                role = get_role_translation(filled, role)
+                role = filled.get_role_translation(role)
                 if role in user.get_roles():
                     return True
         return False
 
     def get_message(self, filled, position='top'):
         if not (self.message and self.position == position and self.is_for_current_user(filled)):
             return ''
 
-- 
2.29.2

