backoffice: expand form_attachments_* in inspect (#16507) #684
|
@ -1,3 +1,4 @@
|
|||
import io
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
|
@ -13,7 +14,12 @@ from wcs.qommon.http_request import HTTPRequest
|
|||
from wcs.qommon.upload_storage import PicklableUpload
|
||||
from wcs.wf.create_formdata import Mapping
|
||||
from wcs.workflow_traces import WorkflowTrace
|
||||
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef, WorkflowCriticalityLevel
|
||||
from wcs.workflows import (
|
||||
AttachmentEvolutionPart,
|
||||
Workflow,
|
||||
WorkflowBackofficeFieldsFormDef,
|
||||
WorkflowCriticalityLevel,
|
||||
)
|
||||
|
||||
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
|
||||
from .test_all import create_user
|
||||
|
@ -246,6 +252,64 @@ def test_inspect_page(pub, local_user):
|
|||
== 'http://example.net/backoffice/workflows/%s/backoffice-fields/fields/bo1/' % workflow.id
|
||||
)
|
||||
|
||||
# test form_attachments
|
||||
formdata.evolution[-1].parts = [
|
||||
AttachmentEvolutionPart(
|
||||
'hello.txt', fp=io.BytesIO(b'test'), content_type='text/plain', varname='testfile'
|
||||
)
|
||||
]
|
||||
formdata.store()
|
||||
resp = app.get('%sinspect' % formdata.get_url(backoffice=True), status=200)
|
||||
assert resp.pyquery('[title="form_attachments"]').length == 0
|
||||
assert (
|
||||
resp.pyquery('[title="form_attachments_testfile"]').parents('li').children('div.value span').text()
|
||||
== 'hello.txt (file)'
|
||||
)
|
||||
assert (
|
||||
resp.pyquery('[title="form_attachments_testfile_content_type"]')
|
||||
.parents('li')
|
||||
.children('div.value span')
|
||||
.text()
|
||||
== 'text/plain'
|
||||
)
|
||||
assert (
|
||||
'/attachment?'
|
||||
in resp.pyquery('[title="form_attachments_testfile_url"]')
|
||||
.parents('li')
|
||||
.children('div.value span')
|
||||
.text()
|
||||
)
|
||||
assert resp.pyquery('[title="form_attachments_0_testfile"]').length == 0
|
||||
|
||||
formdata.evolution[-1].parts.append(
|
||||
AttachmentEvolutionPart(
|
||||
'hello2.txt', fp=io.BytesIO(b'test'), content_type='text/plain', varname='testfile'
|
||||
)
|
||||
)
|
||||
formdata.store()
|
||||
resp = app.get('%sinspect' % formdata.get_url(backoffice=True), status=200)
|
||||
assert (
|
||||
resp.pyquery('[title="form_attachments_testfile_base_filename"]')
|
||||
.parents('li')
|
||||
.children('div.value span')
|
||||
.text()
|
||||
== 'hello2.txt'
|
||||
)
|
||||
assert (
|
||||
resp.pyquery('[title="form_attachments_testfile_0_base_filename"]')
|
||||
.parents('li')
|
||||
.children('div.value span')
|
||||
.text()
|
||||
== 'hello.txt'
|
||||
)
|
||||
assert (
|
||||
resp.pyquery('[title="form_attachments_testfile_1_base_filename"]')
|
||||
.parents('li')
|
||||
.children('div.value span')
|
||||
.text()
|
||||
== 'hello2.txt'
|
||||
)
|
||||
|
||||
# test tools
|
||||
resp = app.get('%sinspect' % formdata.get_url(backoffice=True), status=200)
|
||||
assert 'Test tool' in resp.text
|
||||
|
|
|
@ -650,7 +650,7 @@ def test_formdata_generated_document_odt_download_with_substitution_variable(pub
|
|||
assert content_disposition.split(';')[0] == 'attachment'
|
||||
assert resp.request.environ['PATH_INFO'].endswith(file1.filename)
|
||||
|
||||
file2 = attachments.created_doc[1]
|
||||
file2 = attachments.created_doc[0]
|
||||
|
||||
assert file2.content == response1.body
|
||||
assert file2.b64_content == base64.b64encode(response1.body)
|
||||
assert file2.content_type == response1._headers['content-type']
|
||||
|
|
|
@ -1362,20 +1362,21 @@ def test_register_comment_attachment(pub):
|
|||
assert error.kind == 'deprecated_usage'
|
||||
assert error.occurences_count == 1
|
||||
|
||||
pub.substitutions.feed(formdata)
|
||||
item.comment = '{{ form_attachments.testfile.url }}'
|
||||
item.comment = '{{ form_attachments.testfile.url }}' # check dotted name
|
||||
item.perform(formdata)
|
||||
url2 = formdata.evolution[-1].parts[-1].content
|
||||
assert pub.loggederror_class.count() == 1
|
||||
error = pub.loggederror_class.select()[0]
|
||||
assert error.kind == 'deprecated_usage'
|
||||
assert error.occurences_count == 1
|
||||
assert pub.loggederror_class.count() == 1 # no new error
|
||||
|
||||
item.comment = '{{ form_attachments_testfile_url }}' # check underscored name
|
||||
item.perform(formdata)
|
||||
url3 = formdata.evolution[-1].parts[-1].content
|
||||
assert pub.loggederror_class.count() == 1 # no new error
|
||||
|
||||
assert len(os.listdir(os.path.join(get_publisher().app_dir, 'attachments'))) == 1
|
||||
for subdir in os.listdir(os.path.join(get_publisher().app_dir, 'attachments')):
|
||||
assert len(subdir) == 4
|
||||
assert len(os.listdir(os.path.join(get_publisher().app_dir, 'attachments', subdir))) == 1
|
||||
assert url1 == url2
|
||||
assert url1 == url2 == url3
|
||||
|
||||
# test with a condition
|
||||
item.comment = '{% if form_attachments.testfile %}file is there{% endif %}'
|
||||
|
|
|
@ -3885,7 +3885,7 @@ class FormBackOfficeStatusPage(FormStatusPage):
|
|||
'inspect_url': v['form'].backoffice_url + 'inspect',
|
||||
'display_name': v['form_display_name'],
|
||||
}
|
||||
elif hasattr(v, 'inspect_keys'):
|
||||
elif hasattr(v, 'inspect_keys') and not getattr(v, 'include_in_inspect', False):
|
||||
fpeters
commented
Introduction de cet attribut "include_in_inspect" sans quoi le form_attachments_xxx n'apparait pas (parce qu'il a une méthode inspect_keys) et que j'ai trouvé que c'était quand même utile d'exposer la variable pour la donnée complexe "fichier". Introduction de cet attribut "include_in_inspect" sans quoi le form_attachments_xxx n'apparait pas (parce qu'il a une méthode inspect_keys) et que j'ai trouvé que c'était quand même utile d'exposer la variable pour la donnée complexe "fichier".
|
||||
# skip as there are expanded identifiers
|
||||
continue
|
||||
else:
|
||||
|
|
|
@ -1204,6 +1204,8 @@ def xml_response(obj, filename, content_type='text/xml'):
|
|||
|
||||
|
||||
def get_type_name(value):
|
||||
from wcs.workflows import AttachmentSubstitutionProxy, NamedAttachmentsSubstitutionProxy
|
||||
|
||||
from .upload_storage import PicklableUpload
|
||||
|
||||
object_type_names = {
|
||||
|
@ -1218,6 +1220,8 @@ def get_type_name(value):
|
|||
list: _('list'),
|
||||
str: _('string'),
|
||||
PicklableUpload: _('file'),
|
||||
AttachmentSubstitutionProxy: _('file'),
|
||||
NamedAttachmentsSubstitutionProxy: _('file'),
|
||||
fpeters
commented
Pour afficher le type dans l'inspecteur, sans distinguer. Pour afficher le type dans l'inspecteur, sans distinguer.
|
||||
}
|
||||
object_type_name = object_type_names.get(value.__class__, value.__class__.__name__)
|
||||
return object_type_name
|
||||
|
|
|
@ -171,6 +171,9 @@ class AttachmentSubstitutionProxy:
|
|||
self.formdata = formdata
|
||||
self.attachment_evolution_part = attachment_evolution_part
|
||||
|
||||
def __str__(self):
|
||||
return self.base_filename
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return self.attachment_evolution_part.orig_filename
|
||||
|
@ -204,21 +207,42 @@ class AttachmentSubstitutionProxy:
|
|||
def get_content(self):
|
||||
return self.content
|
||||
|
||||
def inspect_keys(self):
|
||||
return ['url', 'base_filename', 'content_type']
|
||||
|
||||
|
||||
class NamedAttachmentsSubstitutionProxy:
|
||||
include_in_inspect = True # force display in inspect
|
||||
|
||||
def __init__(self, formdata, parts):
|
||||
self.formdata = formdata
|
||||
self.parts = parts[:]
|
||||
self.parts.reverse()
|
||||
self.parts = parts
|
||||
|
||||
def __len__(self):
|
||||
return len(self.parts)
|
||||
|
||||
def __str__(self):
|
||||
return str(self[-1])
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self[0], name)
|
||||
try:
|
||||
return super().__getattr__(name)
|
||||
except AttributeError:
|
||||
return getattr(self[-1], name)
|
||||
fpeters
commented
On passe du On passe du `self[0]` qui visait le premier de la liste passée par .reverse() à `self[-1]` maintenant que la liste n'est plus inversée.
|
||||
|
||||
def inspect_keys(self):
|
||||
yield from self[-1].inspect_keys()
|
||||
if len(self.parts) > 1:
|
||||
# only advertise indexed keys if there are multiple elements
|
||||
yield from [str(x) for x in range(len(self.parts))]
|
||||
fpeters
commented
Pour afficher uniquement form_attachments_xxx quand il y a un seul fichier attaché pour ce nom de variable, et form_attachments_xxx_0_... form_attachments_xxx_1_... etc. en plus quand il y a plusieurs fichiers attachés. Pour afficher uniquement form_attachments_xxx quand il y a un seul fichier attaché pour ce nom de variable, et form_attachments_xxx_0_... form_attachments_xxx_1_... etc. en plus quand il y a plusieurs fichiers attachés.
|
||||
|
||||
def __getitem__(self, i):
|
||||
return AttachmentSubstitutionProxy(self.formdata, self.parts[i])
|
||||
if isinstance(i, int) or (isinstance(i, str) and misc.is_ascii_digit(i)):
|
||||
fpeters
commented
Les clés retournées par inspect_keys doivent être des chaines pour être interprétées correctement par CompatibilityNamesDict, ici donc on gère soit les entiers soit les chaines avec un entier (positif). Les clés retournées par inspect_keys doivent être des chaines pour être interprétées correctement par CompatibilityNamesDict, ici donc on gère soit les entiers soit les chaines avec un entier (positif).
|
||||
return AttachmentSubstitutionProxy(self.formdata, self.parts[int(i)])
|
||||
try:
|
||||
return self.__getattr__(i)
|
||||
except AttributeError:
|
||||
raise KeyError(i)
|
||||
|
||||
|
||||
class AttachmentsSubstitutionProxy:
|
||||
|
@ -226,6 +250,16 @@ class AttachmentsSubstitutionProxy:
|
|||
self.formdata = formdata
|
||||
self.deprecated_usage = deprecated_usage
|
||||
|
||||
def inspect_keys(self):
|
||||
if self.deprecated_usage:
|
||||
return []
|
||||
|
||||
return {
|
||||
part.varname
|
||||
for part in self.formdata.iter_evolution_parts()
|
||||
if isinstance(part, AttachmentEvolutionPart) and getattr(part, 'varname', None)
|
||||
}
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name.startswith('__'):
|
||||
raise AttributeError(name)
|
||||
|
|
Il y avait un reverse() qui est retiré, pour avoir les fichiers attachés indexés chronologiquement, il y a juste ce petit bout de test qui demande adaptation.