backoffice: list related forms and cards in inspect view (#43359)

This commit is contained in:
Lauréline Guérin 2020-07-31 11:28:02 +02:00
parent a1c0fd02a0
commit aa5ffe8cc4
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
4 changed files with 301 additions and 43 deletions

View File

@ -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)

View File

@ -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):

View File

@ -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]

View File

@ -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')