Commit 7d5f7a57 authored by Tomáš Peterka's avatar Tomáš Peterka

[hal_json] Support parameters in Action URLs

parent 95a4470c
...@@ -5,7 +5,7 @@ Responsible for validating form data and redirecting to the form action. ...@@ -5,7 +5,7 @@ Responsible for validating form data and redirecting to the form action.
Please note that the new UI has deprecated use of Selections. Your scripts Please note that the new UI has deprecated use of Selections. Your scripts
will no longer receive `selection_name` nor `selection` in their arguments. will no longer receive `selection_name` nor `selection` in their arguments.
""" """
from Products.ERP5Type.Log import log, DEBUG, INFO, WARNING from Products.ERP5Type.Log import log, DEBUG, INFO, WARNING, ERROR
from Products.Formulator.Errors import FormValidationError, ValidationError from Products.Formulator.Errors import FormValidationError, ValidationError
from ZTUtils import make_query from ZTUtils import make_query
import json import json
...@@ -26,6 +26,7 @@ request = kw.get('REQUEST', None) or container.REQUEST ...@@ -26,6 +26,7 @@ request = kw.get('REQUEST', None) or container.REQUEST
request_form = request.form request_form = request.form
error_message = '' error_message = ''
translate = context.Base_translateString translate = context.Base_translateString
portal = context.getPortalObject()
# Make this script work alike no matter if called by a script or a request # Make this script work alike no matter if called by a script or a request
kw.update(request_form) kw.update(request_form)
...@@ -35,7 +36,7 @@ kw.update(request_form) ...@@ -35,7 +36,7 @@ kw.update(request_form)
if not form_id: if not form_id:
# get default form from default view for given context # get default form from default view for given context
default_view_url = str(portal.Base_filterDuplicateActions( default_view_url = str(portal.Base_filterDuplicateActions(
portal.portal_actions.listFilteredActionsFor(traversed_document))['object_view'][0]['url']) portal.portal_actions.listFilteredActionsFor(context))['object_view'][0]['url'])
form_id = default_view_url.split('?', 1)[0].split("/")[-1] form_id = default_view_url.split('?', 1)[0].split("/")[-1]
# We do NOT create, use, or modify Selections! # We do NOT create, use, or modify Selections!
...@@ -45,24 +46,24 @@ if not form_id: ...@@ -45,24 +46,24 @@ if not form_id:
# Exceptions for UI # Exceptions for UI
if dialog_method == 'Base_configureUI': if dialog_method == 'Base_configureUI':
return context.Base_configureUI(form_id=kw['form_id'], return context.Base_configureUI(form_id=form_id,
selection_name=kw['selection_name'], selection_name=kw['selection_name'],
field_columns=kw['field_columns'], field_columns=kw['field_columns'],
stat_columns=kw['stat_columns']) stat_columns=kw['stat_columns'])
# Exceptions for Sort # Exceptions for Sort
if dialog_method == 'Base_configureSortOn': if dialog_method == 'Base_configureSortOn':
return context.Base_configureSortOn(form_id=kw['form_id'], return context.Base_configureSortOn(form_id=form_id,
selection_name=kw['selection_name'], selection_name=kw['selection_name'],
field_sort_on=kw['field_sort_on'], field_sort_on=kw['field_sort_on'],
field_sort_order=kw['field_sort_order']) field_sort_order=kw['field_sort_order'])
# Exceptions for Workflow # Exceptions for Workflow
if dialog_method == 'Workflow_statusModify': if dialog_method == 'Workflow_statusModify':
return context.Workflow_statusModify(form_id=kw['form_id'], return context.Workflow_statusModify(form_id=form_id,
dialog_id=dialog_id) dialog_id=dialog_id)
# Exception for edit relation # Exception for edit relation
if dialog_method == 'Base_editRelation': if dialog_method == 'Base_editRelation':
return context.Base_editRelation(form_id=kw['form_id'], return context.Base_editRelation(form_id=form_id,
field_id=kw['field_id'], field_id=kw['field_id'],
selection_name=kw['list_selection_name'], selection_name=kw['list_selection_name'],
selection_index=kw['selection_index'], selection_index=kw['selection_index'],
...@@ -72,7 +73,7 @@ if dialog_method == 'Base_editRelation': ...@@ -72,7 +73,7 @@ if dialog_method == 'Base_editRelation':
# Exception for create relation # Exception for create relation
# Not used in new UI - relation field implemented using JIO calls from JS # Not used in new UI - relation field implemented using JIO calls from JS
if dialog_method == 'Base_createRelation': if dialog_method == 'Base_createRelation':
return context.Base_createRelation(form_id=kw['form_id'], return context.Base_createRelation(form_id=form_id,
selection_name=kw['list_selection_name'], selection_name=kw['list_selection_name'],
selection_index=kw['selection_index'], selection_index=kw['selection_index'],
base_category=kw['base_category'], base_category=kw['base_category'],
...@@ -84,7 +85,7 @@ if dialog_method == 'Base_createRelation': ...@@ -84,7 +85,7 @@ if dialog_method == 'Base_createRelation':
return_url=kw['cancel_url']) return_url=kw['cancel_url'])
# NO Exception for folder delete # NO Exception for folder delete
# if dialog_method == 'Folder_delete': # if dialog_method == 'Folder_delete':
# return context.Folder_delete(form_id=kw['form_id'], # return context.Folder_delete(form_id=form_id,
# selection_name=kw['selection_name'], # selection_name=kw['selection_name'],
# md5_object_uid_list=kw['md5_object_uid_list']) # md5_object_uid_list=kw['md5_object_uid_list'])
...@@ -102,7 +103,7 @@ try: ...@@ -102,7 +103,7 @@ try:
request.set('editable_mode', 1) request.set('editable_mode', 1)
form.validate_all_to_request(request) form.validate_all_to_request(request)
request.set('editable_mode', editable_mode) request.set('editable_mode', editable_mode)
default_skin = context.getPortalObject().portal_skins.getDefaultSkin() default_skin = portal.portal_skins.getDefaultSkin()
allowed_styles = ("ODT", "ODS", "Hal", "HalRestricted") allowed_styles = ("ODT", "ODS", "Hal", "HalRestricted")
if getattr(getattr(context, dialog_method), 'pt', None) == "report_view" and \ if getattr(getattr(context, dialog_method), 'pt', None) == "report_view" and \
request.get('your_portal_skin', default_skin) not in allowed_styles: request.get('your_portal_skin', default_skin) not in allowed_styles:
...@@ -179,8 +180,11 @@ query = request_form.get("query", None) ...@@ -179,8 +180,11 @@ query = request_form.get("query", None)
fix = int(request_form.get("fix", "0")) fix = int(request_form.get("fix", "0"))
if query != "" or (query == "" and fix > 0): # force empty query when fix == 1 if query != "" or (query == "" and fix > 0): # force empty query when fix == 1
listbox = getattr(context, form_id).Form_getListbox() listbox = getattr(context, form_id).Form_getListbox()
kw['uids'] = [int(getattr(document, "uid")) if listbox is not None:
for document in context.Base_searchUsingListbox(listbox, query)] kw['uids'] = [int(getattr(document, "uid"))
for document in context.Base_searchUsingListbox(listbox, query)]
else:
log('Action {} should not specify `uids` as its parameters when it does not take object list from the previous view!'.format(dialog_method), level=ERROR)
elif query == "" and fix == 0: elif query == "" and fix == 0:
return context.Base_renderMessage(translate("All documents are selected! Submit again to proceed or Cancel and narrow down your search"), WARNING) return context.Base_renderMessage(translate("All documents are selected! Submit again to proceed or Cancel and narrow down your search"), WARNING)
...@@ -189,7 +193,7 @@ elif query == "" and fix == 0: ...@@ -189,7 +193,7 @@ elif query == "" and fix == 0:
# if dialog_category is object_search, then edit the selection # if dialog_category is object_search, then edit the selection
if dialog_category == "object_search" : if dialog_category == "object_search" :
context.portal_selections.setSelectionParamsFor(kw['selection_name'], kw) portal.portal_selections.setSelectionParamsFor(kw['selection_name'], kw)
# Remove empty values for make_query. # Remove empty values for make_query.
clean_kw = dict((k, v) for k, v in kw.items() if v not in (None, [], ())) clean_kw = dict((k, v) for k, v in kw.items() if v not in (None, [], ()))
...@@ -231,7 +235,7 @@ if True: ...@@ -231,7 +235,7 @@ if True:
# manually, # manually,
if 'portal_skin' in clean_kw: if 'portal_skin' in clean_kw:
new_skin_name = clean_kw['portal_skin'] new_skin_name = clean_kw['portal_skin']
context.getPortalObject().portal_skins.changeSkin(new_skin_name) portal.portal_skins.changeSkin(new_skin_name)
request.set('portal_skin', new_skin_name) request.set('portal_skin', new_skin_name)
deferred_portal_skin = clean_kw.get('deferred_portal_skin') deferred_portal_skin = clean_kw.get('deferred_portal_skin')
if deferred_portal_skin: if deferred_portal_skin:
...@@ -262,7 +266,7 @@ if True: ...@@ -262,7 +266,7 @@ if True:
# RJS: If skin selection is different than Hal* then ERP5Document_getHateoas # RJS: If skin selection is different than Hal* then ERP5Document_getHateoas
# does not exist and we call form method directly # does not exist and we call form method directly
# If update_method was clicked and the target is the original dialog form then we must not call dialog_form directly because it returns HTML # If update_method was clicked and the target is the original dialog form then we must not call dialog_form directly because it returns HTML
if clean_kw.get("portal_skin", context.getPortalObject().portal_skins.getDefaultSkin()) not in ("Hal", "HalRestricted", "View"): if clean_kw.get("portal_skin", portal.portal_skins.getDefaultSkin()) not in ("Hal", "HalRestricted", "View"):
return dialog_form(**kw) return dialog_form(**kw)
# dialog_form can be anything from a pure python function, class method to ERP5 Form or Python Script # dialog_form can be anything from a pure python function, class method to ERP5 Form or Python Script
......
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
</item> </item>
<item> <item>
<key> <string>_params</string> </key> <key> <string>_params</string> </key>
<value> <string>dialog_method, dialog_id, form_id, dialog_category=\'\', update_method=None, query=\'\', **kw</string> </value> <value> <string>dialog_method, dialog_id, form_id=\'\', dialog_category=\'\', update_method=None, query=\'\', **kw</string> </value>
</item> </item>
<item> <item>
<key> <string>id</string> </key> <key> <string>id</string> </key>
......
...@@ -501,6 +501,35 @@ def getRealRelativeUrl(document): ...@@ -501,6 +501,35 @@ def getRealRelativeUrl(document):
return '/'.join(portal.portal_url.getRelativeContentPath(document)) return '/'.join(portal.portal_url.getRelativeContentPath(document))
def parseActionUrl(url):
"""Parse usual ERP5 Action URL into components: ~root, context~, view_id, param_dict, url.
:param url: {str} is expected to be in form https://<site_root>/context/view_id?optional=params
"""
param_dict = {}
url_and_params = url.split(site_root.absolute_url())[-1].split('?')
_, script = url_and_params[0].strip("/ ").rsplit('/', 1)
if len(url_and_params) > 1:
for param in url_and_params[1].split('&'):
param_name, param_value = param.split('=')
if "+" in param_value:
param_value = param_value.replace("+", " ")
if ":" in param_name:
param_name, param_type = param_name.split(":")
if param_type == "int":
param_value = int(param_value)
elif param_type == "bool":
param_value = True if param_value.lower() in ("true", "1") else False
else:
raise ValueError("Cannot convert param {}={} to type {}. Feel free to add implemetation at the position of this exception.".format(
param_name, param_value, param_type))
param_dict[param_name] = param_value
return {
'view_id': script,
'params': param_dict,
'url': url
}
def getFormRelativeUrl(form): def getFormRelativeUrl(form):
return portal.portal_catalog( return portal.portal_catalog(
portal_type=("ERP5 Form", "ERP5 Report"), portal_type=("ERP5 Form", "ERP5 Report"),
...@@ -514,7 +543,7 @@ def getFormRelativeUrl(form): ...@@ -514,7 +543,7 @@ def getFormRelativeUrl(form):
def getFieldDefault(form, field, key, value=None): def getFieldDefault(form, field, key, value=None):
"""Get available value for `field` preferably in python-object from REQUEST or from field's default.""" """Get available value for `field` preferably in python-object from REQUEST or from field's default."""
if value is None: if value is None:
value = REQUEST.form.get(field.id, REQUEST.form.get(key, MARKER)) value = REQUEST.get(field.id, REQUEST.get(key, MARKER))
# use marker because default value can be intentionally empty string # use marker because default value can be intentionally empty string
if value is MARKER: if value is MARKER:
value = field.get_value('default', request=REQUEST, REQUEST=REQUEST) value = field.get_value('default', request=REQUEST, REQUEST=REQUEST)
...@@ -1172,45 +1201,6 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti ...@@ -1172,45 +1201,6 @@ def renderForm(traversed_document, form, response_dict, key_prefix=None, selecti
if value is not None: if value is not None:
REQUEST.set(key, value) REQUEST.set(key, value)
# XXX form action update, etc
def renderRawField(field):
meta_type = field.meta_type
return {
"meta_type": field.meta_type
}
if meta_type == "MethodField":
result = {
"meta_type": field.meta_type
}
else:
result = {
"meta_type": field.meta_type,
"_values": field.values,
# XXX TALES expression is not JSON serializable by default
# "_tales": field.tales
"_overrides": field.overrides
}
if meta_type == "ProxyField":
result['_delegated_list'] = field.delegated_list
# try:
# result['_delegated_list'].pop('list_method')
# except KeyError:
# pass
# XXX ListMethod is not JSON serialized by default
try:
result['_values'].pop('list_method')
except KeyError:
pass
try:
result['_overrides'].pop('list_method')
except KeyError:
pass
return result
def renderFormDefinition(form, response_dict): def renderFormDefinition(form, response_dict):
"""Form "definition" is configurable in Zope admin: Form -> Order.""" """Form "definition" is configurable in Zope admin: Form -> Order."""
...@@ -1221,7 +1211,7 @@ def renderFormDefinition(form, response_dict): ...@@ -1221,7 +1211,7 @@ def renderFormDefinition(form, response_dict):
field_list = [] field_list = []
for field in form.get_fields_in_group(group['goid'], include_disabled=1): for field in form.get_fields_in_group(group['goid'], include_disabled=1):
field_list.append((field.id, renderRawField(field))) field_list.append((field.id, {'meta_type': field.meta_type}))
group_list.append((group['gid'], field_list)) group_list.append((group['gid'], field_list))
response_dict["group_list"] = group_list response_dict["group_list"] = group_list
...@@ -1316,22 +1306,26 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None, ...@@ -1316,22 +1306,26 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
) )
response.setStatus(401) response.setStatus(401)
return "" return ""
elif mime_type != traversed_document.Base_handleAcceptHeader([mime_type]): elif mime_type != traversed_document.Base_handleAcceptHeader([mime_type]):
response.setStatus(406) response.setStatus(406)
return "" return ""
elif (mode == 'root') or (mode == 'traverse'): elif (mode == 'root') or (mode == 'traverse'):
################################################# ##
# Raw document # Render ERP Document with a `view` specified
################################################# # `view` contains view's name and we extract view's URL (we suppose form ${object_url}/Form_view)
# which after expansion gives https://<site-root>/context/view_id?optional=params
if (REQUEST is not None) and (REQUEST.other['method'] != "GET"): if (REQUEST is not None) and (REQUEST.other['method'] != "GET"):
response.setStatus(405) response.setStatus(405)
return "" return ""
# Default properties shared by all ERP5 Document and Site # Default properties shared by all ERP5 Document and Site
action_dict = {} current_action = {} # current action parameters (context, script, URL params)
# result_dict['_relative_url'] = traversed_document.getRelativeUrl() action_dict = {} # actions available on current `traversed_document`
result_dict['title'] = traversed_document.getTitle() result_dict['title'] = traversed_document.getTitle()
# Add a link to the portal type if possible # Add a link to the portal type if possible
...@@ -1364,24 +1358,19 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None, ...@@ -1364,24 +1358,19 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
"name": Base_translateString(container.getTitle()), "name": Base_translateString(container.getTitle()),
} }
# Extract embedded form in the document view # Find current action URL and extract embedded view
embedded_url = None
erp5_action_dict = portal.Base_filterDuplicateActions( erp5_action_dict = portal.Base_filterDuplicateActions(
portal.portal_actions.listFilteredActionsFor(traversed_document)) portal.portal_actions.listFilteredActionsFor(traversed_document))
for erp5_action_key in erp5_action_dict.keys(): for erp5_action_key in erp5_action_dict.keys():
for view_action in erp5_action_dict[erp5_action_key]: for view_action in erp5_action_dict[erp5_action_key]:
# Try to embed the form in the result # Try to embed the form in the result
if (view == view_action['id']): if (view == view_action['id']):
embedded_url = '%s' % view_action['url'] current_action = parseActionUrl('%s' % view_action['url']) # current action/view being rendered
# If there is a "form_id" in the REQUEST then it means that last view was actually a form
# `form_id` should be actually called `dialog_id` in case of form dialogs # and we are most likely in a dialog. We save previous form into `last_form_id` ...
# so real form_id of a previous view stays untouched.
# Here we save previous form_id to `last_form_id` so it does not get overriden by `dialog_id`
last_form_id = REQUEST.get('form_id', "") if REQUEST is not None else "" last_form_id = REQUEST.get('form_id', "") if REQUEST is not None else ""
last_listbox = None last_listbox = None
# So we can do some magic with it! Namely get previous selection (if exists) and deprecated # ... so we can do some magic with it (especially embedded listbox if exists)!
# selection_name which is often required (for e.g. Folder_viewWorkflowActionDialog)
try: try:
if last_form_id: if last_form_id:
last_form = getattr(context, last_form_id) last_form = getattr(context, last_form_id)
...@@ -1389,46 +1378,43 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None, ...@@ -1389,46 +1378,43 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
except AttributeError: except AttributeError:
pass pass
form_id = "" # If we have current action definition we are able to render embedded view
if (embedded_url is not None): # which should be a "ERP5 Form" but in reality can be anything
# XXX Try to fetch the form in the traversed_document of the document if current_action.get('view_id', ''):
# Of course, this code will completely crash in many cases (page template view_instance = getattr(traversed_document, current_action['view_id'])
# instead of form, unexpected action TALES expression). Happy debugging. if (view_instance is not None):
# renderer_form_relative_url = view_action['url'][len(portal.absolute_url()):]
form_id = embedded_url.split('?', 1)[0].split("/")[-1]
# renderer_form = traversed_document.restrictedTraverse(form_id, None)
# XXX Proxy field are not correctly handled in traversed_document of web site
renderer_form = getattr(traversed_document, form_id)
if (renderer_form is not None):
embedded_dict = { embedded_dict = {
'_links': { '_links': {
'self': { 'self': {
'href': embedded_url 'href': current_action['url']
} }
} }
} }
# In order not to use Selections we need to pass all search attributes to *a listbox inside the form dialog*
# in case there was a listbox in the previous form. No other case!
if getattr(view_instance, "pt", "") == "form_dialog" and last_form_id and last_listbox:
# If a Lisbox's list_method takes `uid` as input parameter then it will be ready in the request. But the actual
# computation is too expensive so we make it lazy (and evaluate any callable at `selectKwargsForCallable`)
REQUEST.set("uid", lazyUidList(traversed_document, last_listbox, query))
# Put all query parameters (?reset:int=1&workflow_action=start_action) in request to mimic usual form display # Put all query parameters (?reset:int=1&workflow_action=start_action) in request to mimic usual form display
query_param_dict = {} # Request is later used for method's arguments discovery so set URL params into REQUEST (just like it was sent by form)
query_split = embedded_url.split('?', 1) for query_key, query_value in current_action['params'].items():
if len(query_split) == 2: REQUEST.set(query_key, query_value)
for query_parameter in query_split[1].split("&"):
query_key, query_value = query_parameter.split('=')
# often + is used instead of %20 so we replace for space here
query_param_dict[query_key] = query_value.replace("+", " ")
# If our "form" is actually a Script (nothing is sure in ERP5) then execute it here # If our "form" is actually a Script (nothing is sure in ERP5) then execute it here
try: try:
if "Script" in renderer_form.meta_type: if "Script" in view_instance.meta_type:
# we suppose that the script takes only what is given in the URL params # we suppose that the script takes only what is given in the URL params
return renderer_form(**query_param_dict) return view_instance(**current_action['params'])
except AttributeError: except AttributeError:
# if renderer form does not have attr meta_type then it is not a document # if renderer form does not have attr meta_type then it is not a document
# but most likely bound instance method. Some form_ids do actually point to methods. # but most likely bound instance method. Some form_ids do actually point to methods.
returned_value = renderer_form(**query_param_dict) returned_value = view_instance(**current_action['params'])
# returned value is usually REQUEST.RESPONSE.redirect() # returned value is usually REQUEST.RESPONSE.redirect()
log('ERP5Document_getHateoas', 'HAL_JSON cannot handle returned value "{!s}" from {}({!s})'.format( log('ERP5Document_getHateoas', 'HAL_JSON cannot handle returned value "{!s}" from {}({!s})'.format(
returned_value, form_id, query_param_dict), 100) returned_value, current_action['view_id'], current_action['params']), 100)
status_message = Base_translateString('Operation executed') status_message = Base_translateString('Operation executed')
if isinstance(returned_value, (str, unicode)) and returned_value.startswith('http'): if isinstance(returned_value, (str, unicode)) and returned_value.startswith('http'):
parsed_url = urlparse(returned_value) parsed_url = urlparse(returned_value)
...@@ -1438,19 +1424,9 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None, ...@@ -1438,19 +1424,9 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
return traversed_document.Base_redirect(keep_items={ return traversed_document.Base_redirect(keep_items={
'portal_status_message': status_message}) 'portal_status_message': status_message})
# In order not to use Selections we need to pass all search attributes to *a listbox inside form dialog*
# in case there was a listbox in the previous form. No other case!
if getattr(renderer_form, "pt", "") == "form_dialog" and last_form_id and last_listbox:
# If a Lisbox's list_method takes `uid` as input parameter then it will be ready in the request. But the actual
# computation is too expensive so we make it lazy (and evaluate any callable at `selectKwargsForCallable`)
query_param_dict["uid"] = lazyUidList(traversed_document, last_listbox, query)
# Request is later used for method's arguments discovery so set URL params into REQUEST (just like it was sent by form)
for query_key, query_value in query_param_dict.items():
REQUEST.set(query_key, query_value)
# Embedded Form can be a Script or even a class method thus we mitigate here # Embedded Form can be a Script or even a class method thus we mitigate here
renderForm(traversed_document, renderer_form, embedded_dict) renderForm(traversed_document, view_instance, embedded_dict)
result_dict['_embedded'] = { result_dict['_embedded'] = {
'_view': embedded_dict '_view': embedded_dict
...@@ -1479,17 +1455,19 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None, ...@@ -1479,17 +1455,19 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
if erp5_action_key not in ("view", "object_view", "object_jio_view"): if erp5_action_key not in ("view", "object_view", "object_jio_view"):
# previous view's form_id required almost everything but other views # previous view's form_id required almost everything but other views
url_template_key = "traverse_generator_non_view" url_template_key = "traverse_generator_non_view"
# XXX This line is only optimization for shorter URL and thus is ugly # but when we do not have the last form id we do not pass is of course
if not (form_id or last_form_id): if not (current_action.get('view_id', '') or last_form_id):
url_template_key = "traverse_generator" url_template_key = "traverse_generator"
erp5_action_list[-1]['href'] = url_template_dict[url_template_key] % { erp5_action_list[-1]['href'] = url_template_dict[url_template_key] % {
"root_url": site_root.absolute_url(), "root_url": site_root.absolute_url(),
"script_id": script.id, "script_id": script.id, # this script (ERP5Document_getHateoas)
"relative_url": traversed_document.getRelativeUrl().replace("/", "%2F"), "relative_url": traversed_document.getRelativeUrl().replace("/", "%2F"),
"view": erp5_action_list[-1]['name'], "view": erp5_action_list[-1]['name'],
# add form_id for actions going from module view (form_list) and from document view (form_view) # add form_id for actions going from module view (form_list) and from document view (form_view)
"form_id": form_id if form_id and renderer_form.pt in ("form_view", "form_list") else last_form_id "form_id": (current_action['view_id']
if current_action.get('view_id', '') and view_instance.pt in ("form_view", "form_list")
else last_form_id)
} }
if erp5_action_key == 'object_jump': if erp5_action_key == 'object_jump':
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment