backoffice: expand form_attachments_* in inspect (#16507) #684

Merged
fpeters merged 1 commits from wip/16507-form-attachments-in-inspect into main 2023-09-18 10:49:48 +02:00
6 changed files with 117 additions and 14 deletions

View File

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

View File

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

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.

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.
assert file2.content == response1.body
assert file2.b64_content == base64.b64encode(response1.body)
assert file2.content_type == response1._headers['content-type']

View File

@ -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 %}'

View File

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

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:

View File

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

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

View File

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

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.

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))]
Review

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

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)