diff --git a/bt5/erp5_web/SkinTemplateItem/portal_skins/erp5_web/WebSection_getPermanentURL.py b/bt5/erp5_web/SkinTemplateItem/portal_skins/erp5_web/WebSection_getPermanentURL.py
index a495b1122eae6dfde2d4b1ff31ee0ae244c6346c..8f7c0f52a16b522948db1cd3341d8df35eb4b2a9 100644
--- a/bt5/erp5_web/SkinTemplateItem/portal_skins/erp5_web/WebSection_getPermanentURL.py
+++ b/bt5/erp5_web/SkinTemplateItem/portal_skins/erp5_web/WebSection_getPermanentURL.py
@@ -60,4 +60,6 @@ if document.getValidationState() not in validation_state:
 web_section = context.getWebSectionValue()
 if web_section is None:
   web_section = context
-return "%s/%s" % (web_section.absolute_url(), reference)
+return web_section.constructUrlFor(
+  document_reference=reference
+)
diff --git a/bt5/erp5_web/SkinTemplateItem/portal_skins/erp5_web/WebSite_login.zpt b/bt5/erp5_web/SkinTemplateItem/portal_skins/erp5_web/WebSite_login.zpt
index bc95dceda2146f859cb5fac3757fae6ad7190822..502e54ef1a7da4cde08ca321349c023419475496 100644
--- a/bt5/erp5_web/SkinTemplateItem/portal_skins/erp5_web/WebSite_login.zpt
+++ b/bt5/erp5_web/SkinTemplateItem/portal_skins/erp5_web/WebSite_login.zpt
@@ -10,8 +10,9 @@
              came_from_valid python: not came_from or url_topmost_document.isURLAncestorOf(came_from);">
     <tal:block tal:condition="isAnon">
       <tal:block tal:define="dummy python: response.expireCookie('__ac', path='/');
-                 url python: '%s/login_form?portal_status_message=%s' % (here.absolute_url(), here.Base_translateString('Login and/or password is incorrect.'));
-                 url python: came_from and  '%s&amp;came_from=%s' % (url, came_from) or url;
+                 parameter_dict python: {'portal_status_message': here.Base_translateString('Login and/or password is incorrect.')};
+                 parameter_dict python: parameter_dict.update({'came_from': came_from}) if came_from else parameter_dict;
+                 url python: here.constructUrlFor(form_id='login_form', parameter_dict=parameter_dict);
                  dummy python: response.redirect(url);" />
     </tal:block>
     <tal:block tal:condition="not: isAnon">
@@ -19,8 +20,8 @@
         <tal:block tal:define="dummy python: response.redirect(came_from or here.getPermanentURL(here));" />
       </tal:block>
       <tal:block tal:condition="not: came_from_valid">
-        <tal:block tal:define="dummy python: response.redirect('%s?portal_status_message=%s' % (url_topmost_document.absolute_url(), here.Base_translateString('Redirection to an external site prevented.')));" />
+        <tal:block tal:define="dummy python: response.redirect(url_topmost_document.constructUrlFor(parameter_dict={'portal_status_message': here.Base_translateString('Redirection to an external site prevented.')}))" />
       </tal:block>
     </tal:block>
   </tal:block>
-</tal:block>
\ No newline at end of file
+</tal:block>
diff --git a/bt5/erp5_web_download_theme/SkinTemplateItem/portal_skins/erp5_web_download_theme/WebSection_getPermanentURL.py b/bt5/erp5_web_download_theme/SkinTemplateItem/portal_skins/erp5_web_download_theme/WebSection_getPermanentURL.py
index ac2302c716b95941414eae4a75ce73c51fdd8150..aeb2a2cfdde24b04569910c1faba81de6d4580b2 100644
--- a/bt5/erp5_web_download_theme/SkinTemplateItem/portal_skins/erp5_web_download_theme/WebSection_getPermanentURL.py
+++ b/bt5/erp5_web_download_theme/SkinTemplateItem/portal_skins/erp5_web_download_theme/WebSection_getPermanentURL.py
@@ -4,6 +4,6 @@
 """
 if document.hasReference():
   file_name = document.Document_getStandardFileName()
-  return "%s/%s" % (context.absolute_url(), file_name)
+  return context.constructUrlFor(document_reference=file_name)
 else:
-  return "%s%s" % (document.getAbsoluteUrl(),view and '/view' or '')
+  return context.constructUrlFor(form_id='view' if view else None)
diff --git a/bt5/erp5_web_renderjs_ui/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui/WebSection_renderDefaultPageAsGadget.py b/bt5/erp5_web_renderjs_ui/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui/WebSection_renderDefaultPageAsGadget.py
index 73564d89c2c084a553c9d199d596fee53caaa975..14cecdf05a9069a5674f6426524db03396edf4f5 100644
--- a/bt5/erp5_web_renderjs_ui/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui/WebSection_renderDefaultPageAsGadget.py
+++ b/bt5/erp5_web_renderjs_ui/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui/WebSection_renderDefaultPageAsGadget.py
@@ -15,16 +15,17 @@ default_language = web_section.getLayoutProperty("default_available_language", d
 website_url_set = {}
 
 #simplify code of Base_doLanguage, can't ues Base_doLanguage directly
-root_website_url = web_section.getOriginalDocument().absolute_url()
+root_website_url = web_section.getOriginalDocument().constructUrlFor()
 website_url_pattern = r'^%s(?:%s)*(/|$)' % (
   re.escape(root_website_url),
   '|'.join('/' + re.escape(x) for x in available_language_set))
 
 for language in available_language_set:
+  web_section_url = web_section.constructUrlFor()
   if language == default_language:
-    website_url_set[language] = re.sub(website_url_pattern, r'%s/\1' % root_website_url, web_section.absolute_url())
+    website_url_set[language] = re.sub(website_url_pattern, r'%s/\1' % root_website_url, web_section_url)
   else:
-    website_url_set[language]=  re.sub(website_url_pattern, r'%s/%s/\1' % (root_website_url, language), web_section.absolute_url())
+    website_url_set[language]=  re.sub(website_url_pattern, r'%s/%s/\1' % (root_website_url, language), web_section_url)
 
 view_as_web_method = default_web_page.getTypeBasedMethod(
   "viewAsWeb",
diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_callDialogMethod.py b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_callDialogMethod.py
index d2e6fceceac04d20820666e7ee462ab82e8148ba..4ccc79183cce2a4f5e03bab87303596679a5d81b 100644
--- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_callDialogMethod.py
+++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_callDialogMethod.py
@@ -192,13 +192,11 @@ if dialog_method != update_method and clean_kw.get('deferred_style', 0):
     request.set('deferred_style_dialog_method', dialog_method)
     dialog_method = 'Base_activateSimpleView'
 
-url_params_string = make_query(clean_kw)
-
 # XXX: We always redirect in report mode to make sure portal_skin
 # parameter is taken into account by SkinTool.
 # If url is too long, we do not redirect to avoid crash.
 # XXX: 2000 is an arbitrary value resulted from trial and error.
-if (not(can_redirect) or len(url_params_string) > 2000):
+if (not(can_redirect) or len(make_query(clean_kw)) > 2000):
   if dialog_method != update_method:
     # When we are not executing the update action, we have to change the skin
     # manually,
@@ -226,22 +224,11 @@ if (not(can_redirect) or len(url_params_string) > 2000):
   request.set('URL', '%s/%s' % (context.absolute_url(), dialog_method))
   return dialog_form(**kw)
 
-if error_message != '':
-  redirect_url = '%s/%s?%s' % ( context.absolute_url()
-                              , dialog_method
-                              , 'portal_status_message=%s' % error_message
-                              )
-elif url_params_string != '':
-  redirect_url = '%s/%s?%s' % ( context.absolute_url()
-                              , dialog_method
-                              , url_params_string
-                              )
-
-else:
-  redirect_url = '%s/%s' % ( context.absolute_url()
-                            , dialog_method
-                            )
-
+redirect_url = context.constructUrlFor(
+    form_id=dialog_method,
+    parameter_dict={'portal_status_message': error_message} if error_message != '' \
+      else clean_kw
+  )
 return request.RESPONSE.redirect(redirect_url)
 
 # vim: syntax=python
diff --git a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_edit.py b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_edit.py
index 31e780a8b8b7873ac00b83efb6c998cfae6cc47c..fe58272974fd8f03e561486b0771bb3be0a3df0e 100644
--- a/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_edit.py
+++ b/product/ERP5/bootstrap/erp5_core/SkinTemplateItem/portal_skins/erp5_core/Base_edit.py
@@ -25,9 +25,16 @@ if dialog_id not in ('', None):
 # Prevent users who don't have rights to edit the object from
 # editing it by calling the Base_edit script with correct
 # parameters directly.
+
 if not silent_mode and not request.AUTHENTICATED_USER.has_permission('Modify portal content', context) :
-  msg = Base_translateString("You do not have the permissions to edit the object.")
-  redirect_url = '%s/%s?selection_index=%s&selection_name=%s&%s' % (context.absolute_url(), form_id, selection_index, selection_name, 'portal_status_message=%s' % msg)
+  redirect_url = context.constructUrlFor(
+    form_id=form_id,
+    parameter_dict={
+      'selection_index': selection_index,
+      'selection_name': selection_name,
+      'portal_status_message': Base_translateString("You do not have the permissions to edit the object.")
+    }
+  )
   return request['RESPONSE'].redirect(redirect_url)
 
 # Get the form
@@ -69,7 +76,7 @@ def editListBox(listbox_field, listbox):
         if hasattr(value, 'edit'):
           encapsulated_editor_list.append(value)
         else:
-          if value == '':        
+          if value == '':
             value = None
           cleaned_v[key] = value
 
@@ -232,7 +239,7 @@ try:
     elif(field_meta_type == 'MatrixBox'):
       editMatrixBox(field, request.get(field.id))
 
-  # Return parsed values 
+  # Return parsed values
   if silent_mode: return (kw, encapsulated_editor_list), 'edit'
 
   # Maybe we should build a list of objects we need
@@ -248,12 +255,6 @@ if message_only:
 
 ignore_layout = int(ignore_layout)
 editable_mode = int(editable_mode)
-spp = context.getPhysicalPath()
-spp =list(spp)
-s_url = request["SERVER_URL"]
-spp.insert(0,s_url)
-#calculate direct the url instead of using absolute_url
-new_url = '/'.join(spp)
 
 # for web mode, we should use 'view' instead of passed form_id
 # after 'Save & View'.
@@ -262,26 +263,26 @@ if context.REQUEST.get('is_web_mode', False) and \
   form_id = 'view'
 
 if not selection_index:
-  redirect_url = '%s/%s?ignore_layout:int=%s&editable_mode:int=%s&portal_status_message=%s' % (
-                                  context.absolute_url(),
-                                  form_id,
-                                  ignore_layout,
-                                  editable_mode,
-                                  message)
-
-
+  redirect_url = context.constructUrlFor(
+    form_id=form_id,
+    parameter_dict={
+      'ignore_layout': ignore_layout,
+      'editable_mode': editable_mode,
+      'portal_status_message': message,
+    }
+  )
 else:
-  redirect_url = '%s/%s?selection_index=%s&selection_name=%s&ignore_layout:int=%s&editable_mode=%s&portal_status_message=%s' % (
-                              context.absolute_url(),
-                              form_id,
-                              selection_index,
-                              selection_name,
-                              ignore_layout,
-                              editable_mode,
-                              message)
-
-
-result = request['RESPONSE'].redirect(redirect_url) 
+  redirect_url = context.constructUrlFor(
+    form_id=form_id,
+    parameter_dict={
+      'selection_index': selection_index,
+      'selection_name': selection_name,
+      'ignore_layout': ignore_layout,
+      'editable_mode': editable_mode,
+      'portal_status_message': message,
+    }
+  )
+result = request['RESPONSE'].redirect(redirect_url)
 
 if silent_mode: return result, 'redirect'
 return result
diff --git a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/Base_doLanguage.py b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/Base_doLanguage.py
index d32ea23eebe465c5d489d98a7fa09e0aa138c6d4..cec867fc9154027397be319a39f3e42f1121a097 100644
--- a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/Base_doLanguage.py
+++ b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/Base_doLanguage.py
@@ -9,7 +9,7 @@ if website is not None and website.isStaticLanguageSelection():
   # Web Mode
   root_website = website.getOriginalDocument()
   default_language = root_website.getDefaultAvailableLanguage()
-  root_website_url = root_website.absolute_url()
+  root_website_url = root_website.constructUrlFor()
   website_url_pattern = r'^%s(?:%s)*(/|$)' % (
     re.escape(root_website_url),
     '|'.join('/' + re.escape(x) for x in root_website.getAvailableLanguageList()))
@@ -24,7 +24,7 @@ if website is not None and website.isStaticLanguageSelection():
     if select_language == default_language:
       redirect_url = root_website_url
     else:
-      redirect_url = root_website_url + '/' + select_language
+      redirect_url = root_website.constructUrlFor(document_reference=select_language)
   return context.REQUEST.RESPONSE.redirect(redirect_url)
 else:
   # ERP5 Mode
diff --git a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/Base_redirect.py b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/Base_redirect.py
index a8d084ffc52144768532dfe2d48c3a69ed98feaa..2878093b694033a92dfb86dceab154efef51a29f 100644
--- a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/Base_redirect.py
+++ b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/Base_redirect.py
@@ -3,30 +3,37 @@
    page from a script. It should probably be extended, reviewed and documented
    so that less code is copied and pasted in dialog scripts.
 
-   TODO: improve API and extensively document. ERP5Site_redirect may 
+   TODO: improve API and extensively document. ERP5Site_redirect may
    be redundant.
 """
-# BBB: originally, form_id was the first positional argument
-if not redirect_url or '/' not in redirect_url:
-  form_id = redirect_url or kw.pop('form_id', None)
-  redirect_url = context.absolute_url()
-  if form_id:
-    redirect_url += '/' + form_id
-
 from ZTUtils import make_query
+
 request = context.getPortalObject().REQUEST
 request_form = request.form
 request_form.update(kw)
 request_form = context.ERP5Site_filterParameterList(request_form)
 request_form.update(keep_items)
+parameter_dict = dict([(k, v) for k, v in request_form.items() if k and v is not None])
 
-parameters = make_query(dict([(k, v) for k, v in request_form.items() if k and v is not None]))
-if len(parameters):
-  if '?' in redirect_url:
-    separator = '&'
-  else:
-    separator = '?'
-  redirect_url = '%s%s%s' % (redirect_url, separator, parameters)
+if not redirect_url or '/' not in redirect_url:
+  # Used when redirect_url is not given explicitely and we redirect to an ERP5 document
+  # BBB: originally, form_id was the first positional argument
+  form_id = redirect_url or kw.pop('form_id', None)
+  redirect_url = context.constructUrlFor(
+    form_id=form_id,
+    parameter_dict=parameter_dict,
+  )
+else:
+  # Used if redirect_url is given explicitely (e.g. if we redirect to external web page)
+  # so constructUrlFor is not usable,
+  # since we have no document to redirect using is as context.
+  parameters = make_query(parameter_dict)
+  if len(parameters):
+    if '?' in redirect_url:
+      separator = '&'
+    else:
+      separator = '?'
+    redirect_url = '%s%s%s' % (redirect_url, separator, parameters)
 
 if abort_transaction:
   from zExceptions import Redirect
diff --git a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/global_definitions.zpt b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/global_definitions.zpt
index 4a14ba9ef826ede2de4a2847bb7733320ca9f973..a4c9e8efbbc119b89adf8971c580f3dee179b328 100644
--- a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/global_definitions.zpt
+++ b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/global_definitions.zpt
@@ -9,7 +9,7 @@
              local_parameter_list  local_parameter_list | python: {};
              action_context    python: portal.restrictedTraverse(request.get('object_path', '?'), here);
       global actions          python: here.Base_filterDuplicateActions(portal.portal_actions.listFilteredActionsFor(action_context));
-      global url              here/absolute_url;
+      global url              python: here.constructUrlFor();
       global current_form_id  python: local_parameter_list.get('dialog_id', local_parameter_list.get('form_id', 'view'));
            dummy            python: request.set('current_form_id', current_form_id);
            global current_url      python: '%s/%s' % (url, current_form_id);
diff --git a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/logged_in.py b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/logged_in.py
index c5ed70e2b238253de48dfa4cf46e3811169786bc..8dbc9410f0a11b46a26f76676226fdfd382f26e6 100644
--- a/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/logged_in.py
+++ b/product/ERP5/bootstrap/erp5_xhtml_style/SkinTemplateItem/portal_skins/erp5_xhtml_style/logged_in.py
@@ -1,17 +1,22 @@
 portal = context.getPortalObject()
 if portal.portal_skins.updateSkinCookie():
   portal.setupCurrentSkin()
-url = REQUEST.get("came_from")
+came_from = REQUEST.get("came_from")
 if portal.portal_membership.isAnonymousUser():
   RESPONSE.expireCookie("__ac", path="/")
-  url = "%s/login_form?portal_status_message=%s" % (
-    context.absolute_url(),
-    context.Base_translateString("Login and/or password is incorrect.")
-    + ("&amp;came_from=" + url if url else ""))
-elif not url:
-  url = context.absolute_url()
+  parameter_dict = {'portal_status_message': context.Base_translateString("Login and/or password is incorrect.")}
+  if came_from:
+    parameter_dict['came_from'] = came_from
+  url = context.constructUrlFor(
+    form_id='login_form',
+    parameter_dict=parameter_dict
+  )
+elif came_from:
+  url = came_from
+else:
+  url = context.constructUrlFor()
 topmost_url_document = context.Base_getURLTopmostDocumentValue()
 if not topmost_url_document.isURLAncestorOf(url):
-  return context.ERP5Site_redirect(topmost_url_document.absolute_url(),
+  return context.ERP5Site_redirect(topmost_url_document.constructUrlFor(),
     keep_items={'portal_status_message': 'Redirection to an external site prevented.'})
 return RESPONSE.redirect(url)
diff --git a/product/ERP5Type/Core/Folder.py b/product/ERP5Type/Core/Folder.py
index f8b064c5a5c6b8a40d1c55e359d9b93a0958c8e0..933b3ef6411ce76be01f8d7556e9ac74d3b3a3a0 100644
--- a/product/ERP5Type/Core/Folder.py
+++ b/product/ERP5Type/Core/Folder.py
@@ -74,6 +74,7 @@ import warnings
 from urlparse import urlparse
 REINDEX_SPLIT_COUNT = 100 # if folder containes more than this, reindexing should be splitted.
 from Products.ERP5Type.Message import translateString
+from ZTUtils import make_query
 
 # Dummy Functions for update / upgrade
 def dummyFilter(object,REQUEST=None):
@@ -496,6 +497,48 @@ class FolderMixIn(ExtensionClass.Base):
         raise AccessControl_Unauthorized(method_id)
     return self._recurseCallMethod(method_id, restricted=True, *args, **kw)
 
+  security.declarePublic('constructUrlFor')
+  def constructUrlFor(
+    self,
+    form_id=None,
+    document_reference=None,
+    parameter_dict=None,
+    **kw
+  ):
+    '''
+      Utility method to help in the construction of urls.
+      It should be run in the context of a document that we want as base for the url.
+      Arguments:
+      - form_id: the form that is invoked
+      - document_reference: the reference of a document that is rendered,
+      - parameter_dict: dictionary containing all get parameters
+      Other parameters can be used for type based methods.
+    '''
+    # Try to get type based method, but not for ERP5 Site, since
+    # _getTypeBasedMethod is defined in Base so not available for ERP5 Site
+    if self.getPortalType() != 'ERP5 Site':
+      method = self._getTypeBasedMethod('constructUrlFor')
+      if method is not None:
+        return method(
+          form_id=form_id,
+          document_reference=document_reference,
+          parameter_dict=parameter_dict,
+          **kw
+        )
+    url = self.absolute_url()
+    # form_id and document_reference are mutually exclusive,
+    # we should not expect both in a url
+    assert not (form_id and document_reference), 'Not allowed to have both form and document in the same url'
+    # Note that form_id and document_reference are handled the same way,
+    # it is different variable for semantic reasons
+    if form_id:
+      url = '%s/%s' % (url, form_id)
+    elif document_reference:
+      url = '%s/%s' % (url, document_reference)
+    if parameter_dict:
+      url = '%s?%s' % (url, make_query(parameter_dict))
+    return url
+
   security.declarePublic('isURLAncestorOf')
   def isURLAncestorOf(self, given_url):
     """
@@ -506,7 +549,7 @@ class FolderMixIn(ExtensionClass.Base):
       not access any document in given path,
       hence it does not compute the inner acquisition path.
     """
-    document_url = self.absolute_url()
+    document_url = self.constructUrlFor()
     parsed_given_url = urlparse(given_url)
     parsed_document_url = urlparse(document_url)
     # XXX note that the following check:
diff --git a/product/ERP5Type/tests/testERP5Type.py b/product/ERP5Type/tests/testERP5Type.py
index c3eeaf7048c1cdbecd751ce382039dbe04680f46..6da23c5f45ffd57dcfe5bb7eeeffe0cfb001873b 100644
--- a/product/ERP5Type/tests/testERP5Type.py
+++ b/product/ERP5Type/tests/testERP5Type.py
@@ -3244,6 +3244,54 @@ return [
       # but this did not affect the other role
       self.assertTrue(hasRole(role2))
 
+    def test_constructUrlFor(self):
+      portal_url = self.portal.absolute_url()
+      # check url built for portal
+      portal_url = self.portal.absolute_url()
+      self.assertEqual(self.portal.constructUrlFor(), '%s' % portal_url)
+      # check urls built for a module
+      module_id = 'test_module'
+      module = self.portal.newContent(portal_type='Folder', id=module_id)
+      module_url = module.absolute_url()
+      self.assertEqual(
+        module.constructUrlFor(),
+        '%s/%s' % (portal_url, module_id)
+      )
+      # check url built for a form
+      form_id = 'view'
+      self.assertEqual(
+        module.constructUrlFor(form_id=form_id),
+        '%s/%s' % (module_url, form_id)
+      )
+      # check url built for a document reference
+      file_reference = 'test_file_reference'
+      self.portal.test_module.newContent(
+        portal_type='File',
+        reference=file_reference
+      )
+      self.assertEqual(
+        module.constructUrlFor(document_reference=file_reference),
+        '%s/%s' % (module_url, file_reference)
+      )
+      # check that form_id and document_reference are exclusive
+      # check url built with GET parameters
+      self.assertRaises(
+        AssertionError,
+        module.constructUrlFor,
+        form_id=form_id,
+        document_reference=file_reference
+      )
+      parameter_dict = {'ignore_layout': 1, 'came_from': 'foo/bar'}
+      self.assertIn(
+        module.constructUrlFor(
+          parameter_dict=parameter_dict,
+        ),
+        (
+          '%s/%s?came_from=foo/bar&ignore_layout:int=1' % (portal_url, module_id),
+          '%s/%s?ignore_layout:int=1&came_from=foo/bar' % (portal_url, module_id),
+        )
+      )
+
 class TestAccessControl(ERP5TypeTestCase):
   # Isolate test in a dedicaced class in order not to break other tests
   # when this one fails.