backoffice: list related forms and cards in inspect view (#43359)
This commit is contained in:
parent
a1c0fd02a0
commit
aa5ffe8cc4
|
@ -3,9 +3,7 @@ import datetime
|
|||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import time
|
||||
import hashlib
|
||||
import random
|
||||
import xml.etree.ElementTree as ET
|
||||
import zipfile
|
||||
|
@ -18,24 +16,21 @@ try:
|
|||
except ImportError:
|
||||
xlwt = None
|
||||
|
||||
from django.utils import six
|
||||
from django.utils.six import StringIO, BytesIO
|
||||
from django.utils.six.moves.urllib import parse as urllib
|
||||
from django.utils.six.moves.urllib import parse as urlparse
|
||||
|
||||
from webtest import Upload
|
||||
|
||||
from quixote import cleanup, get_publisher, get_response
|
||||
from quixote import get_publisher
|
||||
from quixote.http_request import Upload as QuixoteUpload
|
||||
from wcs.qommon import ods
|
||||
from wcs.api_utils import sign_url
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.qommon import errors, sessions
|
||||
from wcs.qommon.form import PicklableUpload
|
||||
from wcs.qommon.form import UploadedFile
|
||||
from wcs.qommon.ident.password_accounts import PasswordAccount
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.qommon.substitution import CompatibilityNamesDict
|
||||
from wcs.roles import Role, logged_users_role
|
||||
from wcs.workflows import (Workflow, CommentableWorkflowStatusItem,
|
||||
ChoiceWorkflowStatusItem, EditableWorkflowStatusItem,
|
||||
|
@ -5417,6 +5412,214 @@ def test_inspect_page(pub, local_user):
|
|||
assert 'Invalid block tag' in resp.text
|
||||
|
||||
|
||||
def test_inspect_page_with_related_objects(pub):
|
||||
if not pub.is_using_postgresql():
|
||||
pytest.skip('this requires SQL')
|
||||
return
|
||||
|
||||
user = create_user(pub, is_admin=True)
|
||||
|
||||
FormDef.wipe()
|
||||
CardDef.wipe()
|
||||
|
||||
# test ExternalWorkflowGlobalAction
|
||||
external_wf = Workflow(name='External Workflow')
|
||||
st1 = external_wf.add_status(name='New')
|
||||
action = external_wf.add_global_action('Delete', 'delete')
|
||||
action.append_item('remove')
|
||||
trigger = action.append_trigger('webservice')
|
||||
trigger.identifier = 'delete'
|
||||
external_wf.store()
|
||||
|
||||
external_formdef = FormDef()
|
||||
external_formdef.name = 'External Form'
|
||||
external_formdef.fields = [
|
||||
fields.StringField(id='0', label='string', varname='form_string'),
|
||||
]
|
||||
external_formdef.workflow = external_wf
|
||||
external_formdef.store()
|
||||
|
||||
external_carddef = CardDef()
|
||||
external_carddef.name = 'External Card'
|
||||
external_carddef.fields = [
|
||||
fields.StringField(id='0', label='string', varname='card_string'),
|
||||
]
|
||||
external_carddef.backoffice_submission_roles = user.roles
|
||||
external_carddef.workflow = external_wf
|
||||
external_carddef.store()
|
||||
|
||||
wf = Workflow(name='External actions')
|
||||
st1 = wf.add_status('Create external formdata')
|
||||
create_formdata = CreateFormdataWorkflowStatusItem()
|
||||
create_formdata.label = 'create linked form'
|
||||
create_formdata.formdef_slug = external_formdef.url_name
|
||||
create_formdata.varname = 'created_form'
|
||||
create_formdata.id = '_create_form'
|
||||
mappings = [
|
||||
Mapping(field_id='0', expression='{{ form_var_string }}')
|
||||
]
|
||||
create_formdata.mappings = mappings
|
||||
create_formdata.parent = st1
|
||||
|
||||
create_carddata = CreateCarddataWorkflowStatusItem()
|
||||
create_carddata.label = 'create linked card'
|
||||
create_carddata.formdef_slug = external_carddef.url_name
|
||||
create_carddata.varname = 'created_card'
|
||||
create_carddata.id = '_create_card'
|
||||
create_carddata.mappings = mappings
|
||||
create_carddata.parent = st1
|
||||
|
||||
st1.items.append(create_formdata)
|
||||
st1.items.append(create_carddata)
|
||||
|
||||
global_action = wf.add_global_action('Delete external linked object', 'delete')
|
||||
action = global_action.append_item('external_workflow_global_action')
|
||||
action.slug = 'formdef:%s' % external_formdef.url_name
|
||||
action.trigger_id = 'action:%s' % trigger.identifier
|
||||
wf.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'External action form'
|
||||
formdef.fields = [
|
||||
fields.StringField(id='0', label='string', varname='string'),
|
||||
]
|
||||
formdef.workflow = wf
|
||||
formdef.store()
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'External action card'
|
||||
carddef.fields = [
|
||||
fields.StringField(id='0', label='string', varname='string'),
|
||||
]
|
||||
carddef.backoffice_submission_roles = user.roles
|
||||
carddef.workflow = wf
|
||||
carddef.store()
|
||||
|
||||
assert formdef.data_class().count() == 0
|
||||
assert carddef.data_class().count() == 0
|
||||
assert external_formdef.data_class().count() == 0
|
||||
assert external_carddef.data_class().count() == 0
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'0': 'test form'}
|
||||
formdata.user = user
|
||||
formdata.store()
|
||||
formdata.just_created()
|
||||
formdata.perform_workflow()
|
||||
|
||||
assert formdef.data_class().count() == 1
|
||||
assert carddef.data_class().count() == 0
|
||||
# related form and card
|
||||
assert external_formdef.data_class().count() == 1
|
||||
assert external_carddef.data_class().count() == 1
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/management/external-action-form/1/inspect')
|
||||
assert 'Related Forms/Cards' in resp.text
|
||||
# related form and card
|
||||
assert '<li><a href="http://example.net/backoffice/management/external-form/1/">External Form #1-1</a></li>' in resp.text
|
||||
assert '<li><a href="http://example.net/backoffice/data/external-card/1/">External Card #1-1</a></li>' in resp.text
|
||||
# check related form
|
||||
resp = app.get('/backoffice/management/external-form/1/')
|
||||
assert '<h3>Original form</h3><p><a href="http://example.net/backoffice/management/external-action-form/1/">External action form #2-1</a></p>' in resp.text
|
||||
resp = app.get('/backoffice/management/external-form/1/inspect')
|
||||
# parent
|
||||
assert '<li><a href="http://example.net/backoffice/management/external-action-form/1/">External action form #2-1</a></li>' in resp.text
|
||||
# check related card
|
||||
resp = app.get('/backoffice/data/external-card/1/')
|
||||
assert '<h3>Original form</h3><p><a href="http://example.net/backoffice/management/external-action-form/1/">External action form #2-1</a></p>' in resp.text
|
||||
resp = app.get('/backoffice/data/external-card/1/inspect')
|
||||
# parent
|
||||
assert '<li><a href="http://example.net/backoffice/management/external-action-form/1/">External action form #2-1</a></li>' in resp.text
|
||||
|
||||
formdef.data_class().wipe()
|
||||
carddef.data_class().wipe()
|
||||
external_formdef.data_class().wipe()
|
||||
external_carddef.data_class().wipe()
|
||||
|
||||
carddata = carddef.data_class()()
|
||||
carddata.data = {'0': 'test card'}
|
||||
carddata.user = user
|
||||
carddata.store()
|
||||
carddata.just_created()
|
||||
carddata.perform_workflow()
|
||||
|
||||
assert formdef.data_class().count() == 0
|
||||
assert carddef.data_class().count() == 1
|
||||
# related form and card
|
||||
assert external_formdef.data_class().count() == 1
|
||||
assert external_carddef.data_class().count() == 1
|
||||
|
||||
resp = app.get('/backoffice/data/external-action-card/1/inspect')
|
||||
assert 'Related Forms/Cards' in resp.text
|
||||
# related form and card
|
||||
assert '<li><a href="http://example.net/backoffice/management/external-form/2/">External Form #1-2</a></li>' in resp.text
|
||||
assert '<li><a href="http://example.net/backoffice/data/external-card/2/">External Card #1-2</a></li>' in resp.text
|
||||
# check related form
|
||||
resp = app.get('/backoffice/management/external-form/2/')
|
||||
assert '<h3>Original card</h3><p><a href="http://example.net/backoffice/data/external-action-card/1/">External action card #2-1</a></p>' in resp.text
|
||||
resp = app.get('/backoffice/management/external-form/2/inspect')
|
||||
# parent
|
||||
assert '<li><a href="http://example.net/backoffice/data/external-action-card/1/">External action card #2-1</a></li>' in resp.text
|
||||
# check related card
|
||||
resp = app.get('/backoffice/data/external-card/2/')
|
||||
assert '<h3>Original card</h3><p><a href="http://example.net/backoffice/data/external-action-card/1/">External action card #2-1</a></p>' in resp.text
|
||||
resp = app.get('/backoffice/data/external-card/2/inspect')
|
||||
# parent
|
||||
assert '<li><a href="http://example.net/backoffice/data/external-action-card/1/">External action card #2-1</a></li>' in resp.text
|
||||
|
||||
FormDef.wipe()
|
||||
CardDef.wipe()
|
||||
Workflow.wipe()
|
||||
|
||||
# test linked Card in datasource
|
||||
wf = Workflow(name='WF')
|
||||
st1 = wf.add_status('NEW')
|
||||
wf.store()
|
||||
wfc = Workflow(name='WFC')
|
||||
wfc.add_status('NEW')
|
||||
wfc.store()
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'CARD A'
|
||||
carddef.fields = [
|
||||
fields.StringField(id='0', label='string', varname='string'),
|
||||
]
|
||||
carddef.backoffice_submission_roles = user.roles
|
||||
carddef.workflow = wfc
|
||||
carddef.store()
|
||||
|
||||
datasource = {'type': 'carddef:%s' % carddef.url_name}
|
||||
formdef = FormDef()
|
||||
formdef.name = 'FORM A'
|
||||
formdef.fields = [
|
||||
fields.ItemField(
|
||||
id='0', label='Card',
|
||||
type='item', varname='card',
|
||||
data_source=datasource),
|
||||
]
|
||||
formdef.workflow = wf
|
||||
formdef.store()
|
||||
|
||||
carddata = carddef.data_class()()
|
||||
carddata.data = {'0': 'Text'}
|
||||
carddata.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'0': '1'}
|
||||
formdata.store()
|
||||
formdata.just_created()
|
||||
formdata.perform_workflow()
|
||||
|
||||
assert formdef.data_class().count() == 1
|
||||
assert carddef.data_class().count() == 1
|
||||
|
||||
resp = app.get('/backoffice/management/form-a/1/inspect')
|
||||
assert 'Related Forms/Cards' in resp.text
|
||||
# related card
|
||||
assert '<li><a href="http://example.net/backoffice/data/card-a/1/">CARD A #1-1</a></li>' in resp.text
|
||||
|
||||
|
||||
def test_workflow_jump_previous(pub):
|
||||
user = create_user(pub)
|
||||
create_environment(pub)
|
||||
|
|
|
@ -2740,17 +2740,23 @@ class FormBackOfficeStatusPage(FormStatusPage):
|
|||
extra_context = formdata.submission_context or {}
|
||||
r += htmltext('<div class="extra-context">')
|
||||
if extra_context.get('orig_formdef_id'):
|
||||
r += htmltext('<h3>%s</h3>') % _('Original form')
|
||||
object_type = extra_context.get('orig_object_type', 'formdef')
|
||||
if object_type == 'formdef':
|
||||
r += htmltext('<h3>%s</h3>') % _('Original form')
|
||||
object_class = FormDef
|
||||
else:
|
||||
r += htmltext('<h3>%s</h3>') % _('Original card')
|
||||
object_class = CardDef
|
||||
try:
|
||||
orig_formdata = FormDef.get(extra_context.get('orig_formdef_id')
|
||||
).data_class().get(extra_context.get('orig_formdata_id'))
|
||||
orig_formdata = object_class.get(
|
||||
extra_context.get('orig_formdef_id')
|
||||
).data_class().get(extra_context.get('orig_formdata_id'))
|
||||
except KeyError:
|
||||
r += htmltext('<p>%s</p>') % _('(deleted)')
|
||||
else:
|
||||
r += htmltext('<p><a href="%s">%s %s</a></p>') % (
|
||||
r += htmltext('<p><a href="%s">%s</a></p>') % (
|
||||
orig_formdata.get_url(backoffice=True),
|
||||
orig_formdata.formdef.name,
|
||||
orig_formdata.get_display_id())
|
||||
orig_formdata.get_display_name())
|
||||
if formdata.submission_channel:
|
||||
r += htmltext('<h3>%s</h3>') % '%s: %s' % (
|
||||
_('Channel'), formdata.get_submission_channel_label())
|
||||
|
@ -3141,6 +3147,17 @@ class FormBackOfficeStatusPage(FormStatusPage):
|
|||
r += htmltext('</ul>')
|
||||
r += htmltext('</div>')
|
||||
|
||||
children = list(self.filled.iter_target_datas())
|
||||
if children:
|
||||
r += htmltext('<div id="inspect-related" class="section">')
|
||||
r += htmltext('<h2>%s</h2>') % _('Related Forms/Cards')
|
||||
r += htmltext('<ul class="form-inspector biglist">')
|
||||
for child in children:
|
||||
r += htmltext('<li><a href="%s">%s</a></li>') % (
|
||||
child.get_url(backoffice=True), child.get_display_name())
|
||||
r += htmltext('</ul>')
|
||||
r += htmltext('</div>')
|
||||
|
||||
return r.getvalue()
|
||||
|
||||
def inspect_tool(self):
|
||||
|
|
|
@ -1195,6 +1195,74 @@ class FormData(StorableObject):
|
|||
for part in evo.parts or []:
|
||||
yield part
|
||||
|
||||
def iter_target_datas(self, objectdef=None, object_type=None, status_item=None):
|
||||
# objectdef, object_type and status_item are provided when called from a workflow action
|
||||
from wcs.wf.create_formdata import LinkedFormdataEvolutionPart
|
||||
from wcs.logged_errors import LoggedError
|
||||
from .carddef import CardDef
|
||||
from .formdef import FormDef
|
||||
|
||||
parent = self.get_parent()
|
||||
if parent and object_type:
|
||||
# looking for a parent of a specific type (workflow action)
|
||||
parent_identifier = '%s:%s' % (parent.formdef.xml_root_node, parent.formdef.url_name)
|
||||
if parent_identifier == object_type:
|
||||
yield parent
|
||||
elif parent:
|
||||
# looking for any parent (inspect page)
|
||||
yield parent
|
||||
|
||||
data_ids = []
|
||||
# search linked objects in data sources
|
||||
for field in self.get_formdef().get_all_fields():
|
||||
linked_id = self.data.get(field.id)
|
||||
if not linked_id:
|
||||
continue
|
||||
data_source = getattr(field, 'data_source', None)
|
||||
if data_source and object_type:
|
||||
# looking for a data_source of a specific type (workflow action)
|
||||
if data_source['type'] == object_type:
|
||||
data_ids.append((data_source['type'], linked_id))
|
||||
elif data_source:
|
||||
# looking for any data_source (inspect page)
|
||||
data_ids.append((data_source['type'], linked_id))
|
||||
|
||||
# search in evolution
|
||||
for part in self.iter_evolution_parts():
|
||||
is_linked = isinstance(part, LinkedFormdataEvolutionPart)
|
||||
part_identifier = '%s:%s' % (part.formdef.xml_root_node, part.formdef.url_name)
|
||||
if is_linked and object_type:
|
||||
# looking for an object of a specific type (workflow action)
|
||||
if part_identifier == object_type:
|
||||
data_ids.append((part_identifier, part.formdata_id))
|
||||
elif is_linked:
|
||||
# looking for any object (inspect page)
|
||||
data_ids.append((part_identifier, part.formdata_id))
|
||||
|
||||
for (slug, target_id) in data_ids:
|
||||
if object_type:
|
||||
# workflow action
|
||||
try:
|
||||
yield objectdef.data_class().get(target_id)
|
||||
except KeyError as e:
|
||||
# use custom error message depending on target type
|
||||
LoggedError.record(_('Could not find linked "%(object_name)s" object by id %(object_id)s') % {
|
||||
'object_name': objectdef.name, 'object_id': target_id},
|
||||
formdata=self, status_item=self, exception=e)
|
||||
else:
|
||||
# inspect page
|
||||
try:
|
||||
obj_type, slug = slug.split(':')
|
||||
if obj_type == 'formdef':
|
||||
obj_class = FormDef
|
||||
elif obj_type == 'carddef':
|
||||
obj_class = CardDef
|
||||
yield obj_class.get_by_urlname(slug).data_class().get(target_id)
|
||||
except ValueError:
|
||||
pass
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def __getattr__(self, attr):
|
||||
try:
|
||||
return self.__dict__[attr]
|
||||
|
|
|
@ -22,7 +22,6 @@ from wcs.qommon.form import SingleSelectWidget
|
|||
from wcs.logged_errors import LoggedError
|
||||
from wcs.workflows import WorkflowStatusItem, perform_items, register_item_class
|
||||
from wcs.workflows import WorkflowGlobalActionWebserviceTrigger, Workflow
|
||||
from wcs.wf.create_formdata import LinkedFormdataEvolutionPart
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.formdef import FormDef
|
||||
|
||||
|
@ -115,36 +114,7 @@ class ExternalWorkflowGlobalAction(WorkflowStatusItem):
|
|||
return _('not completed')
|
||||
|
||||
def iter_target_datas(self, formdata, objectdef):
|
||||
parent = formdata.get_parent()
|
||||
if parent:
|
||||
parent_identifier = '%s:%s' % (parent.formdef.xml_root_node,
|
||||
parent.formdef.url_name)
|
||||
if parent_identifier == self.slug:
|
||||
yield parent
|
||||
|
||||
data_ids = []
|
||||
# search linked objects in data sources
|
||||
for field in formdata.get_formdef().get_all_fields():
|
||||
if getattr(field, 'data_source', None) and field.data_source['type'] == self.slug:
|
||||
linked_id = formdata.data.get(field.id)
|
||||
if linked_id:
|
||||
data_ids.append(linked_id)
|
||||
|
||||
# search in evolution
|
||||
for part in formdata.iter_evolution_parts():
|
||||
if isinstance(part, LinkedFormdataEvolutionPart):
|
||||
part_identifier = '%s:%s' % (part.formdef.xml_root_node, part.formdef.url_name)
|
||||
if part_identifier == self.slug:
|
||||
data_ids.append(part.formdata_id)
|
||||
|
||||
for target_id in data_ids:
|
||||
try:
|
||||
yield objectdef.data_class().get(target_id)
|
||||
except KeyError as e:
|
||||
# use custom error message depending on target type
|
||||
LoggedError.record(_('Could not find linked "%(object_name)s" object by id %(object_id)s') % {
|
||||
'object_name': objectdef.name, 'object_id': target_id},
|
||||
formdata=formdata, status_item=self, exception=e)
|
||||
yield from formdata.iter_target_datas(objectdef=objectdef, object_type=self.slug, status_item=self)
|
||||
|
||||
def get_parameters(self):
|
||||
return ('slug', 'trigger_id', 'condition')
|
||||
|
|
Loading…
Reference in New Issue