Compare commits
374 Commits
fd6a336f5e
...
8a9c1fc8e8
Author | SHA1 | Date |
---|---|---|
Paul Marillonnet | 8a9c1fc8e8 | |
Frédéric Péters | 4d5b309986 | |
Lauréline Guérin | e0857ce653 | |
Frédéric Péters | 8c3374e790 | |
Frédéric Péters | 03435d40a6 | |
Frédéric Péters | 70b7087ad9 | |
Valentin Deniaud | 9afbbccb13 | |
Corentin Sechet | 0c225cf254 | |
Valentin Deniaud | a23457fdbf | |
Frédéric Péters | 9c12c01712 | |
Thomas NOËL | 89b4d350ab | |
Valentin Deniaud | 721bdc4e44 | |
Thomas NOËL | 955f012b3d | |
Valentin Deniaud | 0ed9d5d0a0 | |
Valentin Deniaud | 6fd4b87ff5 | |
Valentin Deniaud | d4c3e7dc4e | |
Valentin Deniaud | 7199e84903 | |
Valentin Deniaud | e76e33808b | |
Valentin Deniaud | 0d82f03e59 | |
Valentin Deniaud | 03669bb847 | |
Frédéric Péters | c0d2d36b3c | |
Frédéric Péters | bdb24e21e9 | |
Frédéric Péters | 3a4b8c9cc7 | |
Frédéric Péters | 083f3cf3dd | |
Frédéric Péters | 520e52d1a7 | |
Frédéric Péters | 69249df789 | |
Frédéric Péters | 6de8f10127 | |
Frédéric Péters | 0ed6455a65 | |
Frédéric Péters | 76b94d7ee8 | |
Valentin Deniaud | 09018961dd | |
Frédéric Péters | df546eb981 | |
Frédéric Péters | 0294c31667 | |
Frédéric Péters | 0667660357 | |
Frédéric Péters | bd8d750953 | |
Valentin Deniaud | 6838b2a135 | |
Valentin Deniaud | 17ae2751a2 | |
Valentin Deniaud | fdc8154527 | |
Valentin Deniaud | 2c62dd8196 | |
Valentin Deniaud | 783f3a8bb4 | |
Valentin Deniaud | a11facd293 | |
Valentin Deniaud | 46610bb775 | |
Valentin Deniaud | ea21213f93 | |
Valentin Deniaud | b51d025422 | |
Frédéric Péters | e9a20e4de9 | |
Frédéric Péters | 3c0e04afe7 | |
Frédéric Péters | 2399c72d27 | |
Frédéric Péters | 75030a2bd7 | |
Frédéric Péters | 5bfc33eb62 | |
Lauréline Guérin | 3477ee2f29 | |
Lauréline Guérin | b4c4181cde | |
Lauréline Guérin | eba79fdc77 | |
Lauréline Guérin | 86f28b8037 | |
Lauréline Guérin | 78f2796266 | |
Lauréline Guérin | aa917a59c4 | |
Corentin Sechet | cf0ee0ca29 | |
Frédéric Péters | 7b45d83bd2 | |
Frédéric Péters | 8efea827a1 | |
Frédéric Péters | 68712c8cd0 | |
Frédéric Péters | e30798deb5 | |
Lauréline Guérin | bb73f23502 | |
Frédéric Péters | 1a4fdc71cf | |
Frédéric Péters | d2b95ce0d0 | |
Benjamin Dauvergne | 2d619766b7 | |
Frédéric Péters | 87e3e9aa51 | |
Frédéric Péters | 0d76883638 | |
Frédéric Péters | 783dab9978 | |
Frédéric Péters | 33d243f6e3 | |
Frédéric Péters | 2954998e48 | |
Frédéric Péters | 48593b4e86 | |
Frédéric Péters | f5422ddef0 | |
Frédéric Péters | 96af0663eb | |
Frédéric Péters | f1471ca20c | |
Frédéric Péters | 8598a77b4e | |
Frédéric Péters | a80dc1f54f | |
Frédéric Péters | bff0dc5d83 | |
Frédéric Péters | 8273b31537 | |
Frédéric Péters | 29026b4c72 | |
Frédéric Péters | 9e2743234d | |
Frédéric Péters | e608131d7a | |
Frédéric Péters | 7750954b2f | |
Frédéric Péters | 7b258dfdc6 | |
Frédéric Péters | 372b4ceece | |
Frédéric Péters | ea20e7bcac | |
Frédéric Péters | c77812450b | |
Frédéric Péters | d9c2fecb5d | |
Frédéric Péters | 2e14b82fe5 | |
Frédéric Péters | afc7e799f3 | |
Frédéric Péters | 84e7f29994 | |
Frédéric Péters | d8398e515b | |
Frédéric Péters | 8fc31b0d81 | |
Frédéric Péters | c0b20c8535 | |
Frédéric Péters | 57e4ed63df | |
Frédéric Péters | a1eb55d19e | |
Frédéric Péters | b1604787c3 | |
Frédéric Péters | d984478436 | |
Frédéric Péters | 1e38afbc6f | |
Valentin Deniaud | ad2e64880e | |
Thomas NOËL | 4ea852afe8 | |
Benjamin Dauvergne | 96bfaea4a7 | |
Frédéric Péters | a130f1d862 | |
Frédéric Péters | 1b976a36a7 | |
Frédéric Péters | 4ba3ebf6b4 | |
Frédéric Péters | f6e228b438 | |
Frédéric Péters | 977cdb0019 | |
Frédéric Péters | 99d1f4c21f | |
Frédéric Péters | 89f17153bf | |
Frédéric Péters | c72f9aed9d | |
Valentin Deniaud | 16e844a049 | |
Frédéric Péters | bf442ecf44 | |
Frédéric Péters | 658aff404e | |
Frédéric Péters | 06d6487bb3 | |
Frédéric Péters | eade8f8dda | |
Frédéric Péters | c24bac7837 | |
Frédéric Péters | de292cc399 | |
Frédéric Péters | 23e66ec078 | |
Frédéric Péters | 36e1f16a31 | |
Serghei Mihai | 1476d21ce1 | |
Frédéric Péters | 59d8b91f8d | |
Frédéric Péters | 8c26581924 | |
Valentin Deniaud | 3d42d456d9 | |
Valentin Deniaud | 0c0807a0d4 | |
Valentin Deniaud | 58df7bf7bb | |
Frédéric Péters | 728afe97b6 | |
Frédéric Péters | c03d216a70 | |
Thomas NOËL | 00584bff12 | |
Frédéric Péters | ab38410f34 | |
Frédéric Péters | 2eb29089a1 | |
Valentin Deniaud | f418c515f5 | |
Valentin Deniaud | d784acba07 | |
Valentin Deniaud | d9267a79ca | |
Frédéric Péters | 9cf2aae477 | |
Frédéric Péters | d10392f0fe | |
Valentin Deniaud | 7dfc90a1e5 | |
Frédéric Péters | da6469bde3 | |
Frédéric Péters | 49b2d0d2e4 | |
Frédéric Péters | 71d3b01834 | |
Frédéric Péters | 4e349f0dc5 | |
Frédéric Péters | f296d3dadd | |
Frédéric Péters | f1bead67ee | |
Frédéric Péters | 8b66e281b8 | |
Frédéric Péters | d02b92c4f2 | |
Frédéric Péters | b48214feac | |
Corentin Sechet | 8a7c779d91 | |
Frédéric Péters | 3e6eeff81c | |
Frédéric Péters | 555ae506e5 | |
Frédéric Péters | 445dac2e9b | |
Frédéric Péters | 16d1e680d0 | |
Frédéric Péters | f4e9e7d3ac | |
Frédéric Péters | 3cb981d8e4 | |
Frédéric Péters | 8f5adc758f | |
Nicolas Roche | eaf83221fb | |
Valentin Deniaud | 09d83b2ba6 | |
Valentin Deniaud | b64d76ba83 | |
Valentin Deniaud | 6da43ddcb9 | |
Valentin Deniaud | 8da31255ed | |
Frédéric Péters | 822010b131 | |
Frédéric Péters | f355b9ca02 | |
Frédéric Péters | 389a9bd165 | |
Valentin Deniaud | 104c1c903a | |
Valentin Deniaud | 47c6188a40 | |
Valentin Deniaud | e08aaca460 | |
Frédéric Péters | 2214a45cde | |
Frédéric Péters | 0b23f89e27 | |
Frédéric Péters | 499adec1bb | |
Frédéric Péters | 585240331f | |
Frédéric Péters | e7260e0a55 | |
Frédéric Péters | eab74359c0 | |
Frédéric Péters | cf98592184 | |
Frédéric Péters | e357a132ef | |
Frédéric Péters | 4d693ea166 | |
Frédéric Péters | bf89021479 | |
Valentin Deniaud | 56fdc6f4b7 | |
Valentin Deniaud | a6fafee67a | |
Valentin Deniaud | 11ad660a8d | |
Valentin Deniaud | ae86b948a3 | |
Lauréline Guérin | 5609d89d4a | |
Lauréline Guérin | b15bef6d06 | |
Frédéric Péters | da17ae78b6 | |
Frédéric Péters | 86cbae7af4 | |
Frédéric Péters | 3fe1b86795 | |
Frédéric Péters | b65fface44 | |
Corentin Sechet | abff2ee364 | |
Frédéric Péters | 211f18c6f6 | |
Frédéric Péters | aeb2d548af | |
Frédéric Péters | 7e7a6616a1 | |
Benjamin Dauvergne | 1538eba0a5 | |
Valentin Deniaud | 0a19edc93e | |
Valentin Deniaud | cac1018c21 | |
Valentin Deniaud | 7e6f15155f | |
Valentin Deniaud | e7f9a625df | |
Valentin Deniaud | f39f15f2b6 | |
Valentin Deniaud | 8f5d6ab0b9 | |
Valentin Deniaud | bcef447e34 | |
Valentin Deniaud | 39c58783fe | |
Valentin Deniaud | 8d709bcc10 | |
Valentin Deniaud | c3f64a5d90 | |
Frédéric Péters | 77aceb466d | |
Valentin Deniaud | 355bee2e14 | |
Valentin Deniaud | 41793afeba | |
Frédéric Péters | b77d7473d9 | |
Frédéric Péters | 5faf53489b | |
Frédéric Péters | 8ee2a8cee6 | |
Frédéric Péters | 8d157a7ae4 | |
Frédéric Péters | c9d020dc1e | |
Frédéric Péters | 7f167a4a42 | |
Frédéric Péters | 04c3d5dc5f | |
Frédéric Péters | 4c99402ac8 | |
Frédéric Péters | 12ae23790a | |
Frédéric Péters | 501297682b | |
Frédéric Péters | cfc791d1cf | |
Frédéric Péters | aa296af3b2 | |
Frédéric Péters | 7de00caaac | |
Frédéric Péters | 8a00c17136 | |
Frédéric Péters | 80046e82a1 | |
Frédéric Péters | 52e17f7354 | |
Frédéric Péters | a94233200c | |
Frédéric Péters | be154efd1f | |
Frédéric Péters | 2b2de9b051 | |
Valentin Deniaud | 40518093bc | |
Lauréline Guérin | 03fd82c82e | |
Corentin Sechet | d081e5c02d | |
Frédéric Péters | 3c08c9b524 | |
Valentin Deniaud | f6725183d5 | |
Valentin Deniaud | cbfd74fb15 | |
Valentin Deniaud | f0d8acd993 | |
Frédéric Péters | 4237e5e02a | |
Frédéric Péters | 21951a6687 | |
Frédéric Péters | 416f871c78 | |
Thomas Jund | ef7fbfaa8d | |
Frédéric Péters | 565cae272f | |
Frédéric Péters | 1e2e60f0cc | |
Frédéric Péters | 8ced65d3e8 | |
Frédéric Péters | 9975013b03 | |
Frédéric Péters | a6c2ff9e8c | |
Frédéric Péters | f811716d49 | |
Frédéric Péters | 42a1b32138 | |
Lauréline Guérin | 0c0818239d | |
Frédéric Péters | cdc9ef7110 | |
Frédéric Péters | 7d045d44af | |
Lauréline Guérin | c958920917 | |
Lauréline Guérin | bb599e486b | |
Frédéric Péters | f77806433f | |
Valentin Deniaud | b55fdc4738 | |
Valentin Deniaud | 58c418d78e | |
Valentin Deniaud | dcc2825245 | |
Valentin Deniaud | 64e327b309 | |
Valentin Deniaud | 7bfcecb497 | |
Valentin Deniaud | 3324868265 | |
Valentin Deniaud | 94c725e691 | |
Valentin Deniaud | 2a28e79cec | |
Valentin Deniaud | 58276bee43 | |
Valentin Deniaud | 9327abebf7 | |
Valentin Deniaud | ba60a7b71d | |
Valentin Deniaud | 905fa40f4a | |
Valentin Deniaud | 6c42a93465 | |
Valentin Deniaud | 1509fb1e7f | |
Valentin Deniaud | 1b831241ed | |
Valentin Deniaud | 15f6f47ac5 | |
Valentin Deniaud | fdf774db42 | |
Frédéric Péters | b821e3bd07 | |
Frédéric Péters | bb73734a64 | |
Valentin Deniaud | 3a3ed59748 | |
Valentin Deniaud | 731e550fcd | |
Valentin Deniaud | e649edfab7 | |
Valentin Deniaud | 4bc1f743a8 | |
Valentin Deniaud | 75aa59218e | |
Valentin Deniaud | 84effca916 | |
Valentin Deniaud | 31d3c64c58 | |
Valentin Deniaud | 6dd39f58b1 | |
Valentin Deniaud | 4479c301f8 | |
Valentin Deniaud | 3882ab0ed1 | |
Valentin Deniaud | 4774fd46e7 | |
Valentin Deniaud | f5c5414b83 | |
Valentin Deniaud | e0a5d0eef8 | |
Valentin Deniaud | 1bc4855675 | |
Frédéric Péters | 80b4de8e9e | |
Frédéric Péters | fe340256ce | |
Frédéric Péters | 704d344569 | |
Pierre Ducroquet | a41e90ac59 | |
Frédéric Péters | bd34baa1ab | |
Frédéric Péters | 1b53659d12 | |
Frédéric Péters | e45dd098b3 | |
Frédéric Péters | 1a26682452 | |
Frédéric Péters | 4574965353 | |
Frédéric Péters | e3fddc53cf | |
Frédéric Péters | a129d118e2 | |
Frédéric Péters | 3734b419d4 | |
Frédéric Péters | ee6543f463 | |
Frédéric Péters | 8b2cd7d0e5 | |
Frédéric Péters | 2f3bd2a38b | |
Emmanuel Cazenave | 4ef907d7f8 | |
Emmanuel Cazenave | 71ba4dbce0 | |
Frédéric Péters | b91a619512 | |
Frédéric Péters | bb7ca2500b | |
Frédéric Péters | 8cb0225292 | |
Frédéric Péters | 193a37a902 | |
Frédéric Péters | 510f68c505 | |
Lauréline Guérin | c28b82745d | |
Pierre Ducroquet | 43538c6920 | |
Frédéric Péters | 44fbe88ea9 | |
Frédéric Péters | d9c5a34cf0 | |
Frédéric Péters | e2d8aecc1c | |
Frédéric Péters | c39839a6a6 | |
Frédéric Péters | 108b249965 | |
Frédéric Péters | fc0baf9389 | |
Frédéric Péters | 4abadd3558 | |
Valentin Deniaud | f3d2056be5 | |
Valentin Deniaud | 9101bd1e69 | |
Valentin Deniaud | 54e78443a6 | |
Valentin Deniaud | b63bc74a4d | |
Frédéric Péters | 501697e26a | |
Frédéric Péters | 9877859d25 | |
Corentin Sechet | e7e3b99fa5 | |
Frédéric Péters | 2900e039ea | |
Frédéric Péters | 1a8b37dc5e | |
Frédéric Péters | 8accadc554 | |
Frédéric Péters | 37c5f7d6f3 | |
Frédéric Péters | 1ea10dea1e | |
Frédéric Péters | 9badf31476 | |
Frédéric Péters | f13c15c50d | |
Frédéric Péters | f7803b73b7 | |
Frédéric Péters | 5e2ab80d4f | |
Frédéric Péters | 55671749ba | |
Valentin Deniaud | cddae1547e | |
Lauréline Guérin | 75b70a58e0 | |
Lauréline Guérin | 1d7b8e4330 | |
Lauréline Guérin | a6e21e53d0 | |
Lauréline Guérin | 1fcf1aff98 | |
Lauréline Guérin | 6d41ed8c74 | |
Frédéric Péters | be22912d7c | |
Frédéric Péters | 839db07c52 | |
Frédéric Péters | 9b6e261616 | |
Frédéric Péters | e689bd3402 | |
Frédéric Péters | 3eacdf75ff | |
Frédéric Péters | 36cc71044c | |
Frédéric Péters | 61fcfee1f5 | |
Frédéric Péters | c6780665cd | |
Frédéric Péters | 92c43526d2 | |
Frédéric Péters | 8b5c92fcc8 | |
Frédéric Péters | 768871a2e5 | |
Frédéric Péters | fe2152b0b6 | |
Frédéric Péters | e4209bca85 | |
Pierre Ducroquet | 0cbe2c9521 | |
Frédéric Péters | 1f5a9074fe | |
Valentin Deniaud | 075c5e5d8c | |
Valentin Deniaud | a6cdb0a2b2 | |
Valentin Deniaud | f47e61428f | |
Emmanuel Cazenave | 7bcfae6890 | |
Frédéric Péters | d54ecd3731 | |
Frédéric Péters | 058c97bc3f | |
Frédéric Péters | 0a96be52fc | |
Frédéric Péters | 79a1e05b24 | |
Frédéric Péters | 2b2d59baed | |
Frédéric Péters | 3d7f20cd6b | |
Frédéric Péters | e7efcc1852 | |
Frédéric Péters | a26804288a | |
Frédéric Péters | 0d6d58e1e2 | |
Frédéric Péters | 5a42561469 | |
Frédéric Péters | 6a175aa5de | |
Frédéric Péters | 9804e66af0 | |
Frédéric Péters | 89d9772388 | |
Frédéric Péters | 8da0f623ba | |
Frédéric Péters | e49a789201 | |
Frédéric Péters | 3395128ad6 | |
Frédéric Péters | 7863fa9186 | |
Frédéric Péters | 71e53e6769 | |
Frédéric Péters | e8f414abba | |
Frédéric Péters | 5e8967c1ab | |
Frédéric Péters | be4dc33514 | |
Frédéric Péters | bf180b0398 | |
Frédéric Péters | 089d83d67e | |
Frédéric Péters | 512c315ced | |
Frédéric Péters | a2e5eda666 | |
Frédéric Péters | bcc34dbe6e |
|
@ -43,7 +43,8 @@ Depends: graphviz,
|
|||
uwsgi-plugin-python3,
|
||||
${misc:Depends},
|
||||
${python3:Depends},
|
||||
Recommends: libreoffice-writer-nogui | libreoffice-writer,
|
||||
Recommends: graphicsmagick,
|
||||
libreoffice-writer-nogui | libreoffice-writer,
|
||||
poppler-utils,
|
||||
python3-docutils,
|
||||
python3-langdetect,
|
||||
|
|
|
@ -8,8 +8,6 @@ export PYBUILD_NAME=wcs
|
|||
|
||||
override_dh_install:
|
||||
dh_install
|
||||
mv $(CURDIR)/debian/wcs/usr/bin/wcsctl.py \
|
||||
$(CURDIR)/debian/wcs/usr/bin/wcsctl
|
||||
mv $(CURDIR)/debian/wcs/usr/bin/manage.py \
|
||||
$(CURDIR)/debian/wcs/usr/lib/wcs/
|
||||
install -d $(CURDIR)/debian/wcs/etc/wcs
|
||||
|
|
|
@ -17,7 +17,6 @@ set -e
|
|||
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
|
||||
DESC="Web Forms Manager"
|
||||
NAME=wcs
|
||||
WCSCTL=/usr/bin/wcsctl
|
||||
DAEMON=/usr/bin/uwsgi
|
||||
RUN_DIR=/run/$NAME
|
||||
PIDFILE=$RUN_DIR/$NAME.pid
|
||||
|
|
|
@ -386,6 +386,7 @@ Une API existe pour récupérer le schéma de données d’un modèle de fiches.
|
|||
"disabled_redirection" : null,
|
||||
"discussion" : false,
|
||||
"drafts_lifespan" : null,
|
||||
"drafts_max_per_user" : null,
|
||||
"enable_tracking_codes" : false,
|
||||
"expiration_date" : null,
|
||||
"fields" : [
|
||||
|
|
|
@ -361,6 +361,16 @@ n’est pas nécessaire de préciser l’identifiant d’un utilisateur.
|
|||
<input>GET https://www.example.net/api/forms/inscriptions/10/?anonymise</input>
|
||||
</screen>
|
||||
|
||||
<p>
|
||||
Par ailleurs, l’API « Liste de formulaires » accepte un paramètre
|
||||
<code>include-anonymised</code> permettant d’inclure (<code>on</code>) ou non
|
||||
(<code>off</code>) les demandes anonymisées dans la liste :
|
||||
</p>
|
||||
|
||||
<screen>
|
||||
<input>GET https://www.example.net/api/forms/inscriptions/list?include-anonymised=on</input>
|
||||
</screen>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="global-data">
|
||||
|
|
2
setup.py
2
setup.py
|
@ -208,7 +208,7 @@ setup(
|
|||
package_dir={'wcs': 'wcs'},
|
||||
packages=find_packages(),
|
||||
cmdclass=cmdclass,
|
||||
scripts=['wcsctl.py', 'manage.py'],
|
||||
scripts=['manage.py'],
|
||||
include_package_data=True,
|
||||
data_files=data_tree('share/wcs/web/', 'data/web/')
|
||||
+ data_tree('share/wcs/themes/', 'data/themes/')
|
||||
|
|
|
@ -2,6 +2,7 @@ import os
|
|||
|
||||
import pytest
|
||||
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.qommon.ident.password_accounts import PasswordAccount
|
||||
|
||||
|
@ -52,6 +53,7 @@ def teardown_module(module):
|
|||
|
||||
|
||||
def test_empty_site(pub):
|
||||
pub.user_class.wipe()
|
||||
resp = get_app(pub).get('/backoffice/users/')
|
||||
resp = resp.click('New User')
|
||||
resp = get_app(pub).get('/backoffice/settings/')
|
||||
|
@ -86,6 +88,10 @@ def test_admin_for_all(pub):
|
|||
user = create_superuser(pub)
|
||||
role = create_role(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test'
|
||||
formdef.store()
|
||||
|
||||
try:
|
||||
with open(os.path.join(pub.app_dir, 'ADMIN_FOR_ALL'), 'w'):
|
||||
pass # create empty file
|
||||
|
|
|
@ -220,6 +220,25 @@ def test_block_export_import(pub):
|
|||
assert 'Invalid File (Unknown referenced objects)' in resp
|
||||
assert '<ul><li>Unknown datasources: foobar</li></ul>' in resp
|
||||
|
||||
# python expression
|
||||
if not pub.site_options.has_section('options'):
|
||||
pub.site_options.add_section('options')
|
||||
pub.site_options.set('options', 'forbid-new-python-expressions', 'true')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
block.fields = [
|
||||
fields.StringField(id='2', label='python_prefill', prefill={'type': 'formula', 'value': '1 + 2'}),
|
||||
]
|
||||
block.store()
|
||||
resp = app.get('/backoffice/forms/blocks/%s/' % block.id)
|
||||
resp = resp.click(href=re.compile('^export$'))
|
||||
xml_export = resp.text
|
||||
resp = app.get('/backoffice/forms/blocks/')
|
||||
resp = resp.click(href='import')
|
||||
resp.form['file'] = Upload('block', xml_export.encode('utf-8'))
|
||||
resp = resp.form.submit()
|
||||
assert 'Python expression detected' in resp
|
||||
|
||||
|
||||
def test_block_delete(pub):
|
||||
create_superuser(pub)
|
||||
|
@ -257,6 +276,55 @@ def test_block_delete(pub):
|
|||
assert 'This block is still used' in resp
|
||||
|
||||
|
||||
def test_block_export_overwrite(pub):
|
||||
create_superuser(pub)
|
||||
BlockDef.wipe()
|
||||
block = BlockDef()
|
||||
block.name = 'foobar'
|
||||
block.fields = [fields.StringField(id='123', required=True, label='Test')]
|
||||
block.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/forms/blocks/%s/' % block.id)
|
||||
resp = resp.click(href=re.compile('^export$'))
|
||||
xml_export = resp.text
|
||||
|
||||
block.slug = 'new-slug'
|
||||
block.name = 'New foobar'
|
||||
block.fields = [fields.StringField(id='123', required=True, label='Test bebore overwrite')]
|
||||
block.store()
|
||||
|
||||
resp = app.get('/backoffice/forms/blocks/%s/' % block.id)
|
||||
resp = resp.click('Overwrite')
|
||||
resp = resp.form.submit('cancel').follow()
|
||||
resp = resp.click('Overwrite')
|
||||
resp = resp.form.submit()
|
||||
assert 'There were errors processing your form.' in resp
|
||||
|
||||
resp.form['file'] = Upload('block', xml_export.encode('utf-8'))
|
||||
resp = resp.form.submit()
|
||||
resp = resp.follow()
|
||||
assert BlockDef.count() == 1
|
||||
|
||||
block.refresh_from_storage()
|
||||
assert block.fields[0].label == 'Test'
|
||||
assert block.name == 'foobar'
|
||||
assert block.slug == 'new-slug' # not overwritten
|
||||
|
||||
# unknown reference
|
||||
block.fields = [fields.StringField(id='1', data_source={'type': 'foobar'})]
|
||||
block.store()
|
||||
resp = app.get('/backoffice/forms/blocks/%s/' % block.id)
|
||||
resp = resp.click(href=re.compile('^export$'))
|
||||
xml_export = resp.text
|
||||
resp = app.get('/backoffice/forms/blocks/%s/' % block.id)
|
||||
resp = resp.click('Overwrite')
|
||||
resp.form['file'] = Upload('block', xml_export.encode('utf-8'))
|
||||
resp = resp.form.submit()
|
||||
assert 'Invalid File (Unknown referenced objects)' in resp
|
||||
assert '<ul><li>Unknown datasources: foobar</li></ul>' in resp
|
||||
|
||||
|
||||
def test_block_edit_duplicate_delete_field(pub):
|
||||
create_superuser(pub)
|
||||
BlockDef.wipe()
|
||||
|
@ -453,14 +521,14 @@ def test_block_edit_field_warnings(pub):
|
|||
blockdef.store()
|
||||
resp = app.get('/backoffice/forms/blocks/%s/' % blockdef.id)
|
||||
assert 'more than 30 fields' not in resp.text
|
||||
assert '<div id="new-field"><h3>New Field</h3>' in resp.text
|
||||
assert resp.pyquery('#new-field')
|
||||
assert resp.pyquery('#fields-list a[title="Duplicate"]').length
|
||||
|
||||
blockdef.fields.extend([fields.StringField(id='%d' % i, label='field %d' % i) for i in range(21, 51)])
|
||||
blockdef.store()
|
||||
resp = app.get('/backoffice/forms/blocks/%s/' % blockdef.id)
|
||||
assert 'This block of fields contains 60 fields.' in resp.text
|
||||
assert '<div id="new-field"><h3>New Field</h3>' not in resp.text
|
||||
assert not resp.pyquery('#new-field')
|
||||
assert not resp.pyquery('#fields-list a[title="Duplicate"]').length
|
||||
|
||||
|
||||
|
@ -574,6 +642,8 @@ def test_block_field_statistics_data_update(pub):
|
|||
|
||||
def test_block_test_results(pub):
|
||||
create_superuser(pub)
|
||||
TestDef.wipe()
|
||||
TestResult.wipe()
|
||||
BlockDef.wipe()
|
||||
block = BlockDef()
|
||||
block.name = 'foobar'
|
||||
|
|
|
@ -404,16 +404,6 @@ def test_card_id_template(pub):
|
|||
carddata.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/cards/1/')
|
||||
resp = resp.click('Templates')
|
||||
assert 'id_template' not in resp.text
|
||||
|
||||
if not pub.site_options.has_section('options'):
|
||||
pub.site_options.add_section('options')
|
||||
pub.site_options.set('options', 'enable-card-identifier-template', 'true')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
|
||||
resp = app.get('/backoffice/cards/1/')
|
||||
resp = resp.click('Templates')
|
||||
assert 'Identifier cannot be modified if there are existing cards.' in resp.text
|
||||
|
@ -437,6 +427,16 @@ def test_card_id_template(pub):
|
|||
carddata.store()
|
||||
assert carddata.id_display == 'XbarY'
|
||||
|
||||
# check option is not advertised if disabled
|
||||
if not pub.site_options.has_section('options'):
|
||||
pub.site_options.add_section('options')
|
||||
pub.site_options.set('options', 'enable-card-identifier-template', 'false')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
resp = app.get('/backoffice/cards/1/')
|
||||
resp = resp.click('Templates')
|
||||
assert 'id_template' not in resp.text
|
||||
|
||||
|
||||
def test_card_digest_template(pub):
|
||||
create_superuser(pub)
|
||||
|
@ -1035,7 +1035,7 @@ def test_card_edit_field_warnings(pub):
|
|||
resp = app.get('/backoffice/cards/%s/fields/' % carddef.id)
|
||||
assert 'more than 200 fields' not in resp.text
|
||||
assert 'first field should be of type "page"' in resp.text
|
||||
assert '<div id="new-field"><h3>New Field</h3>' in resp.text
|
||||
assert resp.pyquery('#new-field')
|
||||
|
||||
carddef.fields.extend([fields.StringField(id='%d' % i, label='field %d' % i) for i in range(10, 210)])
|
||||
carddef.store()
|
||||
|
@ -1049,7 +1049,7 @@ def test_card_edit_field_warnings(pub):
|
|||
resp = app.get('/backoffice/cards/%s/fields/' % carddef.id)
|
||||
assert 'This card model contains 410 fields.' in resp.text
|
||||
assert 'first field should be of type "page"' in resp.text
|
||||
assert '<div id="new-field"><h3>New Field</h3>' not in resp.text
|
||||
assert not resp.pyquery('#new-field')
|
||||
assert '>Duplicate<' not in resp.text
|
||||
|
||||
|
||||
|
|
|
@ -1039,6 +1039,23 @@ def test_data_sources_import(pub):
|
|||
resp = resp.form.submit()
|
||||
assert 'Invalid File' in resp.text
|
||||
|
||||
# python expression
|
||||
if not pub.site_options.has_section('options'):
|
||||
pub.site_options.add_section('options')
|
||||
pub.site_options.set('options', 'forbid-new-python-expressions', 'true')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
data_source.data_source = {'type': 'formula', 'value': repr([('1', 'un'), ('2', 'deux')])}
|
||||
data_source.store()
|
||||
resp = app.get('/backoffice/settings/data-sources/%s/' % data_source.id)
|
||||
resp = resp.click(href=re.compile('^export$'))
|
||||
xml_export = resp.text
|
||||
resp = app.get('/backoffice/settings/data-sources/')
|
||||
resp = resp.click(href='import')
|
||||
resp.form['file'] = Upload('ds', xml_export.encode('utf-8'))
|
||||
resp = resp.form.submit()
|
||||
assert 'Python expression detected' in resp
|
||||
|
||||
|
||||
def test_data_sources_edit_slug(pub):
|
||||
create_superuser(pub)
|
||||
|
|
|
@ -7,11 +7,11 @@ import pytest
|
|||
from quixote.http_request import Upload as QuixoteUpload
|
||||
|
||||
from wcs import fields
|
||||
from wcs.backoffice.deprecations import DeprecationsScanAfterJob
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.backoffice.deprecations import DeprecatedElementsDetected, DeprecationsScan
|
||||
from wcs.blocks import BlockDef, BlockdefImportError
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.data_sources import NamedDataSource
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.data_sources import NamedDataSource, NamedDataSourceImportError
|
||||
from wcs.formdef import FormDef, FormdefImportError
|
||||
from wcs.mail_templates import MailTemplate
|
||||
from wcs.qommon.form import UploadedFile
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
|
@ -23,8 +23,13 @@ from wcs.wf.geolocate import GeolocateWorkflowStatusItem
|
|||
from wcs.wf.jump import JumpWorkflowStatusItem
|
||||
from wcs.wf.notification import SendNotificationWorkflowStatusItem
|
||||
from wcs.wf.redirect_to_url import RedirectToUrlWorkflowStatusItem
|
||||
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef
|
||||
from wcs.wscalls import NamedWsCall
|
||||
from wcs.workflows import (
|
||||
Workflow,
|
||||
WorkflowBackofficeFieldsFormDef,
|
||||
WorkflowImportError,
|
||||
WorkflowVariablesFieldsFormDef,
|
||||
)
|
||||
from wcs.wscalls import NamedWsCall, NamedWsCallImportError
|
||||
|
||||
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
|
||||
from .test_all import create_superuser
|
||||
|
@ -102,6 +107,10 @@ def test_deprecations(pub):
|
|||
workflow.backoffice_fields_formdef.fields = [
|
||||
fields.TableField(id='bo1', label='table field'),
|
||||
]
|
||||
workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow)
|
||||
workflow.variables_formdef.fields = [
|
||||
fields.TableField(id='wfvar1', label='other table field'),
|
||||
]
|
||||
st0 = workflow.add_status('Status0', 'st0')
|
||||
|
||||
display = st0.add_action('displaymsg')
|
||||
|
@ -183,6 +192,9 @@ def test_deprecations(pub):
|
|||
data_source = NamedDataSource(name='ds_jsonp')
|
||||
data_source.data_source = {'type': 'jsonp', 'value': 'xxx'}
|
||||
data_source.store()
|
||||
data_source = NamedDataSource(name='ds_csv')
|
||||
data_source.data_source = {'type': 'json', 'value': 'http://example.net/csvdatasource/plop/test'}
|
||||
data_source.store()
|
||||
|
||||
NamedWsCall.wipe()
|
||||
wscall = NamedWsCall()
|
||||
|
@ -190,6 +202,16 @@ def test_deprecations(pub):
|
|||
wscall.request = {'url': 'http://example.net', 'qs_data': {'a': '=1+2'}}
|
||||
wscall.store()
|
||||
|
||||
wscall = NamedWsCall()
|
||||
wscall.name = 'Hello CSV'
|
||||
wscall.request = {'url': 'http://example.net/csvdatasource/plop/test'}
|
||||
wscall.store()
|
||||
|
||||
wscall = NamedWsCall()
|
||||
wscall.name = 'Hello json data store'
|
||||
wscall.request = {'url': 'http://example.net/jsondatastore/plop'}
|
||||
wscall.store()
|
||||
|
||||
MailTemplate.wipe()
|
||||
mail_template1 = MailTemplate()
|
||||
mail_template1.name = 'Hello1'
|
||||
|
@ -253,11 +275,19 @@ def test_deprecations(pub):
|
|||
assert [x.text for x in resp.pyquery('.section--fields li a')] == [
|
||||
'foobar / Field "table field"',
|
||||
'foobar / Field "ranked field"',
|
||||
'Options of workflow "test" / Field "other table field"',
|
||||
'Backoffice fields of workflow "test" / Field "table field"',
|
||||
]
|
||||
assert [x.text for x in resp.pyquery('.section--actions li a')] == [
|
||||
'test / Daily Summary Email',
|
||||
]
|
||||
assert [x.text for x in resp.pyquery('.section--csv-connector li a')] == [
|
||||
'Data source "ds_csv"',
|
||||
'Webservice "Hello CSV"',
|
||||
]
|
||||
assert [x.text for x in resp.pyquery('.section--json-data-store li a')] == [
|
||||
'Webservice "Hello json data store"',
|
||||
]
|
||||
# check all links are ok
|
||||
for link in resp.pyquery('.section li a'):
|
||||
resp.click(href=link.attrib['href'], index=0)
|
||||
|
@ -273,7 +303,7 @@ def test_deprecations_choice_label(pub):
|
|||
accept = st0.add_action('choice', id='_choice')
|
||||
accept.label = '[test] action'
|
||||
|
||||
job = DeprecationsScanAfterJob()
|
||||
job = DeprecationsScan()
|
||||
job.execute()
|
||||
assert not job.report_lines
|
||||
|
||||
|
@ -285,7 +315,7 @@ def test_deprecations_skip_invalid_ezt(pub):
|
|||
display = st0.add_action('displaymsg')
|
||||
display.message = 'message with invalid [if-any] ezt'
|
||||
|
||||
job = DeprecationsScanAfterJob()
|
||||
job = DeprecationsScan()
|
||||
job.execute()
|
||||
assert not job.report_lines
|
||||
|
||||
|
@ -296,19 +326,19 @@ def test_deprecations_ignore_ezt_looking_tag(pub):
|
|||
sendmail = st0.add_action('sendmail')
|
||||
sendmail.subject = '[REMINDER] your appointment'
|
||||
workflow.store()
|
||||
job = DeprecationsScanAfterJob()
|
||||
job = DeprecationsScan()
|
||||
job.execute()
|
||||
assert not job.report_lines
|
||||
|
||||
sendmail.subject = '[reminder]'
|
||||
workflow.store()
|
||||
job = DeprecationsScanAfterJob()
|
||||
job = DeprecationsScan()
|
||||
job.execute()
|
||||
assert job.report_lines
|
||||
|
||||
sendmail.subject = '[if-any plop]test[end]'
|
||||
workflow.store()
|
||||
job = DeprecationsScanAfterJob()
|
||||
job = DeprecationsScan()
|
||||
job.execute()
|
||||
assert job.report_lines
|
||||
|
||||
|
@ -377,7 +407,7 @@ def test_deprecations_document_models(pub):
|
|||
export_to2.by = ['_submitter']
|
||||
workflow.store()
|
||||
|
||||
job = DeprecationsScanAfterJob()
|
||||
job = DeprecationsScan()
|
||||
job.execute()
|
||||
assert job.report_lines == [
|
||||
{
|
||||
|
@ -426,7 +456,7 @@ def test_deprecations_inspect_pages(pub):
|
|||
display.message = 'message with [ezt] info'
|
||||
workflow.store()
|
||||
|
||||
job = DeprecationsScanAfterJob()
|
||||
job = DeprecationsScan()
|
||||
job.execute()
|
||||
|
||||
create_superuser(pub)
|
||||
|
@ -465,7 +495,7 @@ def test_deprecations_inspect_pages(pub):
|
|||
display.message = 'message with {{django}} info'
|
||||
workflow.store()
|
||||
|
||||
job = DeprecationsScanAfterJob()
|
||||
job = DeprecationsScan()
|
||||
job.execute()
|
||||
|
||||
resp = app.get(formdef.get_admin_url() + 'inspect')
|
||||
|
@ -486,7 +516,7 @@ def test_deprecations_inspect_pages_old_format(pub):
|
|||
]
|
||||
formdef.store()
|
||||
|
||||
job = DeprecationsScanAfterJob()
|
||||
job = DeprecationsScan()
|
||||
job.execute()
|
||||
|
||||
with open(os.path.join(pub.app_dir, 'deprecations.json')) as f:
|
||||
|
@ -505,3 +535,124 @@ def test_deprecations_inspect_pages_old_format(pub):
|
|||
|
||||
resp = app.get('/backoffice/studio/deprecations/')
|
||||
assert resp.pyquery('.section--python-condition li a')
|
||||
|
||||
|
||||
def test_deprecations_on_import(pub):
|
||||
formdef = FormDef()
|
||||
formdef.name = 'foobar'
|
||||
formdef.fields = [
|
||||
fields.PageField(id='1', label='page1', condition={'type': 'python', 'value': 'True'}),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
blockdef = BlockDef()
|
||||
blockdef.name = 'foobar'
|
||||
blockdef.fields = [
|
||||
fields.StringField(id='2', label='python_prefill', prefill={'type': 'formula', 'value': '1 + 2'}),
|
||||
]
|
||||
blockdef.store()
|
||||
|
||||
workflow = Workflow(name='test')
|
||||
st0 = workflow.add_status('Status0', 'st0')
|
||||
sendsms = st0.add_action('sendsms', id='_sendsms')
|
||||
sendsms.to = 'xxx'
|
||||
sendsms.condition = {'type': 'python', 'value': 'True'}
|
||||
sendsms.parent = st0
|
||||
st0.items.append(sendsms)
|
||||
workflow.store()
|
||||
|
||||
data_source = NamedDataSource(name='ds_python')
|
||||
data_source.data_source = {'type': 'formula', 'value': repr([('1', 'un'), ('2', 'deux')])}
|
||||
data_source.store()
|
||||
|
||||
wscall = NamedWsCall()
|
||||
wscall.name = 'Hello'
|
||||
wscall.request = {'url': 'http://example.net', 'qs_data': {'a': '=1+2'}}
|
||||
wscall.store()
|
||||
|
||||
mail_template = MailTemplate() # no python expression in mail templates
|
||||
mail_template.name = 'Hello2'
|
||||
mail_template.subject = 'plop'
|
||||
mail_template.body = 'plop [ezt] plop'
|
||||
mail_template.store()
|
||||
|
||||
job = DeprecationsScan()
|
||||
job.check_deprecated_elements_in_object(formdef)
|
||||
formdef_xml = formdef.export_to_xml()
|
||||
FormDef.import_from_xml_tree(formdef_xml)
|
||||
|
||||
job = DeprecationsScan()
|
||||
job.check_deprecated_elements_in_object(blockdef)
|
||||
blockdef_xml = blockdef.export_to_xml()
|
||||
BlockDef.import_from_xml_tree(blockdef_xml)
|
||||
|
||||
job = DeprecationsScan()
|
||||
job.check_deprecated_elements_in_object(workflow)
|
||||
workflow_xml = workflow.export_to_xml()
|
||||
Workflow.import_from_xml_tree(workflow_xml)
|
||||
|
||||
job = DeprecationsScan()
|
||||
job.check_deprecated_elements_in_object(data_source)
|
||||
data_source_xml = data_source.export_to_xml()
|
||||
NamedDataSource.import_from_xml_tree(data_source_xml)
|
||||
|
||||
job = DeprecationsScan()
|
||||
job.check_deprecated_elements_in_object(wscall)
|
||||
wscall_xml = wscall.export_to_xml()
|
||||
NamedWsCall.import_from_xml_tree(wscall_xml)
|
||||
|
||||
job = DeprecationsScan()
|
||||
job.check_deprecated_elements_in_object(mail_template)
|
||||
mail_template_xml = mail_template.export_to_xml()
|
||||
MailTemplate.import_from_xml_tree(mail_template_xml)
|
||||
|
||||
if not pub.site_options.has_section('options'):
|
||||
pub.site_options.add_section('options')
|
||||
pub.site_options.set('options', 'forbid-new-python-expressions', 'true')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
|
||||
job = DeprecationsScan()
|
||||
with pytest.raises(DeprecatedElementsDetected) as excinfo:
|
||||
job.check_deprecated_elements_in_object(formdef)
|
||||
assert str(excinfo.value) == 'Python expression detected'
|
||||
with pytest.raises(FormdefImportError) as excinfo:
|
||||
FormDef.import_from_xml_tree(formdef_xml)
|
||||
assert str(excinfo.value) == 'Python expression detected'
|
||||
|
||||
job = DeprecationsScan()
|
||||
with pytest.raises(DeprecatedElementsDetected) as excinfo:
|
||||
job.check_deprecated_elements_in_object(blockdef)
|
||||
assert str(excinfo.value) == 'Python expression detected'
|
||||
with pytest.raises(BlockdefImportError) as excinfo:
|
||||
BlockDef.import_from_xml_tree(blockdef_xml)
|
||||
assert str(excinfo.value) == 'Python expression detected'
|
||||
|
||||
job = DeprecationsScan()
|
||||
with pytest.raises(DeprecatedElementsDetected) as excinfo:
|
||||
job.check_deprecated_elements_in_object(workflow)
|
||||
assert str(excinfo.value) == 'Python expression detected'
|
||||
with pytest.raises(WorkflowImportError) as excinfo:
|
||||
Workflow.import_from_xml_tree(workflow_xml)
|
||||
assert str(excinfo.value) == 'Python expression detected'
|
||||
|
||||
job = DeprecationsScan()
|
||||
with pytest.raises(DeprecatedElementsDetected) as excinfo:
|
||||
job.check_deprecated_elements_in_object(data_source)
|
||||
assert str(excinfo.value) == 'Python expression detected'
|
||||
with pytest.raises(NamedDataSourceImportError) as excinfo:
|
||||
NamedDataSource.import_from_xml_tree(data_source_xml)
|
||||
assert str(excinfo.value) == 'Python expression detected'
|
||||
|
||||
job = DeprecationsScan()
|
||||
with pytest.raises(DeprecatedElementsDetected) as excinfo:
|
||||
job.check_deprecated_elements_in_object(wscall)
|
||||
assert str(excinfo.value) == 'Python expression detected'
|
||||
with pytest.raises(NamedWsCallImportError) as excinfo:
|
||||
NamedWsCall.import_from_xml_tree(wscall_xml)
|
||||
assert str(excinfo.value) == 'Python expression detected'
|
||||
|
||||
# no python expressions
|
||||
job = DeprecationsScan()
|
||||
job.check_deprecated_elements_in_object(mail_template)
|
||||
MailTemplate.import_from_xml_tree(mail_template_xml)
|
||||
|
|
|
@ -11,7 +11,7 @@ import responses
|
|||
from pyquery import PyQuery
|
||||
from webtest import Upload
|
||||
|
||||
from wcs import fields
|
||||
from wcs import fields, workflow_tests
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.categories import CardDefCategory, Category, DataSourceCategory, WorkflowCategory
|
||||
|
@ -20,7 +20,8 @@ from wcs.formdef import FormDef
|
|||
from wcs.qommon.afterjobs import AfterJob
|
||||
from wcs.qommon.errors import ConnectionError
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.testdef import TestDef, TestResult
|
||||
from wcs.testdef import TestDef, TestResult, WebserviceResponse
|
||||
from wcs.workflow_tests import WorkflowTests
|
||||
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef, WorkflowVariablesFieldsFormDef
|
||||
from wcs.wscalls import NamedWsCall
|
||||
|
||||
|
@ -241,13 +242,26 @@ def test_forms_edit_management(pub, formdef):
|
|||
# Misc management
|
||||
assert_option_display(resp, 'Management', 'Default')
|
||||
resp = resp.click('Management', href='options/management')
|
||||
assert resp.forms[0]['include_download_all_button'].checked is False
|
||||
resp.forms[0]['include_download_all_button'].checked = True
|
||||
assert resp.forms[0]['management_sidebar_items$elementgeneral'].checked is True
|
||||
assert resp.forms[0]['management_sidebar_items$elementdownload-files'].checked is False
|
||||
resp.forms[0]['management_sidebar_items$elementdownload-files'].checked = True
|
||||
resp = resp.forms[0].submit()
|
||||
assert resp.location == 'http://example.net/backoffice/forms/1/'
|
||||
resp = resp.follow()
|
||||
assert_option_display(resp, 'Management', 'Custom')
|
||||
assert FormDef.get(1).include_download_all_button is True
|
||||
assert 'general' in FormDef.get(1).management_sidebar_items
|
||||
assert 'download-files' in FormDef.get(1).management_sidebar_items
|
||||
|
||||
resp = resp.click('Management', href='options/management')
|
||||
resp.forms[0]['management_sidebar_items$elementgeneral'].checked = False
|
||||
resp = resp.forms[0].submit().follow()
|
||||
assert 'general' not in FormDef.get(1).management_sidebar_items
|
||||
|
||||
resp = resp.click('Management', href='options/management')
|
||||
resp.forms[0]['management_sidebar_items$elementgeneral'].checked = True
|
||||
resp.forms[0]['management_sidebar_items$elementdownload-files'].checked = False
|
||||
resp = resp.forms[0].submit().follow()
|
||||
assert FormDef.get(1).management_sidebar_items == {'__default__'}
|
||||
|
||||
|
||||
def test_forms_edit_tracking_code(pub, formdef):
|
||||
|
@ -270,6 +284,7 @@ def test_forms_edit_tracking_code(pub, formdef):
|
|||
|
||||
resp = resp.click('Form Tracking')
|
||||
assert resp.forms[0]['drafts_lifespan'].value == ''
|
||||
assert resp.forms[0]['drafts_max_per_user'].value == ''
|
||||
resp = resp.forms[0].submit().follow() # check empty value is ok
|
||||
|
||||
resp = resp.click('Form Tracking')
|
||||
|
@ -283,6 +298,20 @@ def test_forms_edit_tracking_code(pub, formdef):
|
|||
resp = resp.forms[0].submit().follow()
|
||||
assert FormDef.get(1).drafts_lifespan == '5'
|
||||
|
||||
resp = resp.click('Form Tracking')
|
||||
resp.forms[0]['drafts_max_per_user'].value = 'xxx'
|
||||
resp = resp.forms[0].submit()
|
||||
assert 'Maximum must be between 2 and 100 drafts.' in resp
|
||||
resp.forms[0]['drafts_max_per_user'].value = '120'
|
||||
resp = resp.forms[0].submit()
|
||||
assert 'Maximum must be between 2 and 100 drafts.' in resp
|
||||
resp.forms[0]['drafts_max_per_user'].value = '1'
|
||||
resp = resp.forms[0].submit()
|
||||
assert 'Maximum must be between 2 and 100 drafts.' in resp
|
||||
resp.forms[0]['drafts_max_per_user'].value = '3'
|
||||
resp = resp.forms[0].submit().follow()
|
||||
assert FormDef.get(1).drafts_max_per_user == '3'
|
||||
|
||||
formdef.fields = [
|
||||
fields.StringField(id='1', label='VerifyString'),
|
||||
fields.DateField(id='2', label='VerifyDate'),
|
||||
|
@ -509,6 +538,34 @@ def test_forms_edit_publication_date(pub):
|
|||
assert 'invalid value' in resp
|
||||
|
||||
|
||||
def test_forms_list_publication_date(pub):
|
||||
create_superuser(pub)
|
||||
create_role(pub)
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = []
|
||||
formdef.publication_date = '2024-03-06 00:00'
|
||||
formdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/forms/')
|
||||
assert resp.pyquery('.publication-dates').text() == 'Published from 2024-03-06 00:00'
|
||||
|
||||
formdef.expiration_date = '2024-03-10 00:00'
|
||||
formdef.store()
|
||||
resp = app.get('/backoffice/forms/')
|
||||
assert (
|
||||
resp.pyquery('.publication-dates').text() == 'Published from 2024-03-06 00:00 until 2024-03-10 00:00'
|
||||
)
|
||||
|
||||
formdef.publication_date = None
|
||||
formdef.store()
|
||||
resp = app.get('/backoffice/forms/')
|
||||
assert resp.pyquery('.publication-dates').text() == 'Published until 2024-03-10 00:00'
|
||||
|
||||
|
||||
def test_form_category(pub):
|
||||
create_superuser(pub)
|
||||
create_role(pub)
|
||||
|
@ -764,7 +821,7 @@ def test_form_workflow_link(pub):
|
|||
|
||||
def test_form_workflow_remapping(pub):
|
||||
AfterJob.wipe()
|
||||
create_superuser(pub)
|
||||
user = create_superuser(pub)
|
||||
create_role(pub)
|
||||
|
||||
FormDef.wipe()
|
||||
|
@ -871,6 +928,8 @@ def test_form_workflow_remapping(pub):
|
|||
resp = resp.follow() # -> to job processing page
|
||||
resp = resp.click('Back')
|
||||
assert resp.pyquery('[href="workflow"] .offset').text() == 'Workflow Three'
|
||||
assert pub.snapshot_class.select_object_history(formdef)[0].comment == 'Workflow change'
|
||||
assert pub.snapshot_class.select_object_history(formdef)[0].user_id == str(user.id)
|
||||
|
||||
# run a SQL SELECT and we known all columns are defined.
|
||||
FormDef.get(formdef.id).data_class().select()
|
||||
|
@ -1495,6 +1554,63 @@ def test_form_duplicate(pub):
|
|||
assert FormDef.get(3).name == 'other copy'
|
||||
|
||||
|
||||
def test_form_duplicate_with_tests(pub):
|
||||
create_superuser(pub)
|
||||
create_role(pub)
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.store()
|
||||
|
||||
TestDef.wipe()
|
||||
testdef = TestDef.create_from_formdata(formdef, formdef.data_class()())
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.ButtonClick(id='1', button_name='xxx'),
|
||||
]
|
||||
testdef.name = 'First test'
|
||||
testdef.store()
|
||||
|
||||
response = WebserviceResponse()
|
||||
response.testdef_id = testdef.id
|
||||
response.name = 'First response'
|
||||
response.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdef.data_class()())
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.AssertStatus(id='1', status_name='yyy'),
|
||||
]
|
||||
testdef.name = 'Second test'
|
||||
testdef.store()
|
||||
|
||||
response = WebserviceResponse()
|
||||
response.testdef_id = testdef.id
|
||||
response.name = 'Second response'
|
||||
response.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/forms/1/')
|
||||
|
||||
resp = resp.click(href='duplicate')
|
||||
resp = resp.form.submit('submit').follow()
|
||||
assert FormDef.count() == 2
|
||||
assert TestDef.count() == 4
|
||||
assert WorkflowTests.count() == 4
|
||||
assert WebserviceResponse.count() == 4
|
||||
|
||||
new_formdef = FormDef.get(2)
|
||||
assert new_formdef.name == 'form title (copy)'
|
||||
|
||||
testdef1, testdef2 = TestDef.select_for_objectdef(new_formdef)
|
||||
assert testdef1.name == 'First test'
|
||||
assert testdef2.name == 'Second test'
|
||||
|
||||
assert testdef1.workflow_tests.actions[0].button_name == 'xxx'
|
||||
assert testdef2.workflow_tests.actions[0].status_name == 'yyy'
|
||||
assert testdef1.get_webservice_responses()[0].name == 'First response'
|
||||
assert testdef2.get_webservice_responses()[0].name == 'Second response'
|
||||
|
||||
|
||||
def test_form_export(pub):
|
||||
create_superuser(pub)
|
||||
create_role(pub)
|
||||
|
@ -1624,6 +1740,90 @@ def test_form_import_from_url(pub):
|
|||
assert FormDef.count() == 1
|
||||
|
||||
|
||||
def test_form_import_with_tests(pub):
|
||||
create_superuser(pub)
|
||||
create_role(pub)
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.store()
|
||||
|
||||
TestDef.wipe()
|
||||
testdef = TestDef.create_from_formdata(formdef, formdef.data_class()())
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.ButtonClick(id='1', button_name='xxx'),
|
||||
]
|
||||
testdef.name = 'First test'
|
||||
testdef.store()
|
||||
|
||||
response = WebserviceResponse()
|
||||
response.testdef_id = testdef.id
|
||||
response.name = 'First response'
|
||||
response.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdef.data_class()())
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.AssertStatus(id='1', status_name='yyy'),
|
||||
]
|
||||
testdef.name = 'Second test'
|
||||
testdef.store()
|
||||
|
||||
response = WebserviceResponse()
|
||||
response.testdef_id = testdef.id
|
||||
response.name = 'Second response'
|
||||
response.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/1/')
|
||||
export_resp = resp.click(href='export')
|
||||
|
||||
FormDef.wipe()
|
||||
TestDef.wipe()
|
||||
WebserviceResponse.wipe()
|
||||
|
||||
resp = app.get('/backoffice/forms/import')
|
||||
resp.forms[0]['file'] = Upload('formdef.wcs', export_resp.body)
|
||||
resp = resp.forms[0].submit()
|
||||
|
||||
assert FormDef.count() == 1
|
||||
formdef = FormDef.get(1)
|
||||
|
||||
testdef1, testdef2 = TestDef.select_for_objectdef(formdef)
|
||||
assert testdef1.name == 'First test'
|
||||
assert testdef2.name == 'Second test'
|
||||
|
||||
# import the same formdef a second time
|
||||
resp = app.get('/backoffice/forms/import')
|
||||
resp.forms[0]['file'] = Upload('formdef.wcs', export_resp.body)
|
||||
resp = resp.forms[0].submit()
|
||||
|
||||
assert FormDef.count() == 2
|
||||
assert TestDef.count() == 4
|
||||
formdef2 = formdef.get(2)
|
||||
|
||||
testdef1, testdef2 = TestDef.select_for_objectdef(formdef2)
|
||||
assert testdef1.name == 'First test'
|
||||
assert testdef2.name == 'Second test'
|
||||
|
||||
assert testdef1.workflow_tests.actions[0].button_name == 'xxx'
|
||||
assert testdef2.workflow_tests.actions[0].status_name == 'yyy'
|
||||
assert testdef1.get_webservice_responses()[0].name == 'First response'
|
||||
assert testdef2.get_webservice_responses()[0].name == 'Second response'
|
||||
|
||||
TestDef.remove_object(testdef1.id)
|
||||
assert TestDef.count() == 3
|
||||
|
||||
# overwrite doesn't impact tests
|
||||
resp = app.get('/backoffice/forms/2/')
|
||||
resp = resp.click(href='overwrite')
|
||||
resp.forms[0]['file'] = Upload('formdef.wcs', export_resp.body)
|
||||
resp = resp.forms[0].submit()
|
||||
|
||||
assert TestDef.count() == 3
|
||||
|
||||
|
||||
def test_form_qrcode(pub):
|
||||
create_superuser(pub)
|
||||
create_role(pub)
|
||||
|
@ -2380,7 +2580,7 @@ def test_form_prefill_field(pub):
|
|||
resp.form['prefill$type'] = 'Python Expression (deprecated)'
|
||||
resp.form['prefill$value_formula'] = ':'
|
||||
resp = resp.form.submit('submit')
|
||||
assert 'invalid expression: unexpected EOF while parsing' in resp.text
|
||||
assert 'invalid expression: invalid syntax' in resp.text
|
||||
|
||||
resp = app.get('/backoffice/forms/1/fields/1/')
|
||||
resp.form['prefill$type'] = 'String / Template'
|
||||
|
@ -3458,7 +3658,7 @@ def test_form_edit_field_warnings(pub):
|
|||
resp = app.get('/backoffice/forms/%s/fields/' % formdef.id)
|
||||
assert 'more than 200 fields' not in resp.text
|
||||
assert 'first field should be of type "page"' in resp.text
|
||||
assert '<div id="new-field"><h3>New Field</h3>' in resp.text
|
||||
assert resp.pyquery('#new-field')
|
||||
|
||||
formdef.fields.extend([fields.StringField(id='%d' % i, label='field %d' % i) for i in range(10, 210)])
|
||||
formdef.store()
|
||||
|
@ -3473,7 +3673,7 @@ def test_form_edit_field_warnings(pub):
|
|||
assert 'This form contains 410 fields.' in resp.text
|
||||
assert 'no new fields can be added.' in resp.text
|
||||
assert 'first field should be of type "page"' in resp.text
|
||||
assert '<div id="new-field"><h3>New Field</h3>' not in resp.text
|
||||
assert not resp.pyquery('#new-field')
|
||||
assert '>Duplicate<' not in resp.text
|
||||
assert resp.pyquery('aside .errornotice')
|
||||
assert not resp.pyquery('aside form[action=new]')
|
||||
|
@ -3486,7 +3686,7 @@ def test_form_edit_field_warnings(pub):
|
|||
|
||||
resp = app.get('/backoffice/forms/%s/fields/' % formdef.id)
|
||||
assert 'no new fields should be added.' in resp.text
|
||||
assert '<div id="new-field"><h3>New Field</h3>' in resp.text
|
||||
assert resp.pyquery('#new-field')
|
||||
assert '>Duplicate<' in resp.text
|
||||
assert not resp.pyquery('aside .errornotice')
|
||||
assert resp.pyquery('aside form[action=new]')
|
||||
|
@ -3892,6 +4092,39 @@ def test_form_overwrite(pub):
|
|||
assert resp.pyquery('.error').text() == 'Invalid File'
|
||||
|
||||
|
||||
def test_form_export_import_export(pub):
|
||||
create_superuser(pub)
|
||||
create_role(pub)
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.table_name = 'xxx'
|
||||
formdef.fields = []
|
||||
formdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
# python expression
|
||||
if not pub.site_options.has_section('options'):
|
||||
pub.site_options.add_section('options')
|
||||
pub.site_options.set('options', 'forbid-new-python-expressions', 'true')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
formdef.fields = [
|
||||
fields.StringField(id='2', label='python_prefill', prefill={'type': 'formula', 'value': '1 + 2'}),
|
||||
]
|
||||
formdef.store()
|
||||
resp = app.get('/backoffice/forms/%s/' % formdef.id)
|
||||
resp = resp.click(href=re.compile('^export$'))
|
||||
xml_export = resp.text
|
||||
resp = app.get('/backoffice/forms/')
|
||||
resp = resp.click(href='import')
|
||||
resp.form['file'] = Upload('formdef', xml_export.encode('utf-8'))
|
||||
resp = resp.form.submit()
|
||||
assert 'Python expression detected' in resp
|
||||
|
||||
|
||||
def test_form_export_import_export_overwrite(pub):
|
||||
create_superuser(pub)
|
||||
create_role(pub)
|
||||
|
@ -3949,6 +4182,23 @@ def test_form_export_import_export_overwrite(pub):
|
|||
field_ow = formdef_overwrited.fields[i]
|
||||
assert (field.id, field.label, field.key) == (field_ow.id, field_ow.label, field_ow.key)
|
||||
|
||||
# python expression
|
||||
if not pub.site_options.has_section('options'):
|
||||
pub.site_options.add_section('options')
|
||||
pub.site_options.set('options', 'forbid-new-python-expressions', 'true')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
formdef2.fields = [
|
||||
fields.StringField(id='2', label='python_prefill', prefill={'type': 'formula', 'value': '1 + 2'}),
|
||||
]
|
||||
formdef2.store()
|
||||
formdef2_xml = ET.tostring(formdef2.export_to_xml(include_id=True))
|
||||
resp = app.get('/backoffice/forms/%s/' % formdef.id)
|
||||
resp = resp.click(href='overwrite')
|
||||
resp.forms[0]['file'] = Upload('formdef.wcs', formdef2_xml)
|
||||
resp = resp.forms[0].submit()
|
||||
assert 'Python expression detected' in resp
|
||||
|
||||
|
||||
def test_form_overwrite_from_url(pub):
|
||||
create_superuser(pub)
|
||||
|
@ -4466,6 +4716,13 @@ def test_admin_form_inspect(pub):
|
|||
|
||||
assert '>Custom views</button>' not in resp
|
||||
|
||||
# check all field links
|
||||
for href in [x.attrib['href'] for x in resp.pyquery('.inspect-field h4 a')]:
|
||||
app.get(href)
|
||||
|
||||
# check field links targets per-page URL
|
||||
assert '/pages/' in resp.pyquery('.inspect-field h4 a')[0].attrib['href']
|
||||
|
||||
custom_view_owner = pub.custom_view_class()
|
||||
custom_view_owner.title = 'card view owner'
|
||||
custom_view_owner.formdef = formdef
|
||||
|
@ -4494,6 +4751,37 @@ def test_admin_form_inspect(pub):
|
|||
assert '<h4>card view any</h4>' in resp
|
||||
assert '<h4>card view datasource</h4>' in resp
|
||||
|
||||
# check with a form without pages
|
||||
formdef.fields = [
|
||||
fields.StringField(
|
||||
id='1', label='String field', varname='var_1', condition={'type': 'django', 'value': 'true'}
|
||||
),
|
||||
]
|
||||
formdef.store()
|
||||
resp = app.get('/backoffice/forms/%s/inspect' % formdef.id)
|
||||
|
||||
# check all field links
|
||||
for href in [x.attrib['href'] for x in resp.pyquery('.inspect-field h4 a')]:
|
||||
app.get(href)
|
||||
|
||||
# check field links targets per-page URL
|
||||
assert '/pages/' not in resp.pyquery('.inspect-field h4 a')[0].attrib['href']
|
||||
|
||||
# check drafts lifespan value
|
||||
assert [
|
||||
PyQuery(x).parent().text()
|
||||
for x in resp.pyquery('.parameter')
|
||||
if x.text == 'Lifespan of drafts (in days):'
|
||||
] == ['Lifespan of drafts (in days): 100']
|
||||
formdef.drafts_lifespan = '40'
|
||||
formdef.store()
|
||||
resp = app.get('/backoffice/forms/%s/inspect' % formdef.id)
|
||||
assert [
|
||||
PyQuery(x).parent().text()
|
||||
for x in resp.pyquery('.parameter')
|
||||
if x.text == 'Lifespan of drafts (in days):'
|
||||
] == ['Lifespan of drafts (in days): 40']
|
||||
|
||||
|
||||
def test_admin_form_inspect_validation(pub):
|
||||
create_superuser(pub)
|
||||
|
|
|
@ -7,7 +7,7 @@ from webtest import Upload
|
|||
from wcs.blocks import BlockDef
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.categories import Category
|
||||
from wcs.fields import ItemField, StringField
|
||||
from wcs.fields import ItemField, PageField, StringField
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.i18n import TranslatableMessage
|
||||
from wcs.mail_templates import MailTemplate
|
||||
|
@ -30,7 +30,15 @@ def pub():
|
|||
pub.cfg['language'] = {'language': 'en', 'multilinguism': True, 'languages': ['en', 'fr']}
|
||||
pub.write_cfg()
|
||||
|
||||
TranslatableMessage.do_table()
|
||||
TranslatableMessage.do_table() # update table with selected languages
|
||||
|
||||
TranslatableMessage.wipe()
|
||||
Workflow.wipe()
|
||||
FormDef.wipe()
|
||||
BlockDef.wipe()
|
||||
Category.wipe()
|
||||
CardDef.wipe()
|
||||
MailTemplate.wipe()
|
||||
|
||||
return pub
|
||||
|
||||
|
@ -67,9 +75,19 @@ def test_i18n_page(pub):
|
|||
action2.triggers = []
|
||||
workflow.store()
|
||||
|
||||
workflow2 = Workflow(name='second workflow')
|
||||
workflow2.add_status('Other Status')
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.fields = [
|
||||
PageField(
|
||||
id='0',
|
||||
label='page field',
|
||||
post_conditions=[
|
||||
{'condition': {'type': 'django', 'value': 'blah'}, 'error_message': 'page error message'},
|
||||
],
|
||||
),
|
||||
StringField(id='1', label='text field'),
|
||||
StringField(
|
||||
id='2',
|
||||
|
@ -114,6 +132,9 @@ def test_i18n_page(pub):
|
|||
# check 'text field' only appears one
|
||||
assert TranslatableMessage.count([Equal('string', 'text field')]) == 1
|
||||
|
||||
# check page post condition
|
||||
assert TranslatableMessage.count([Equal('string', 'page error message')]) == 1
|
||||
|
||||
# check global action name appears only if there's a manual trigger
|
||||
assert TranslatableMessage.count([Equal('string', 'Global Manual')]) == 1
|
||||
assert TranslatableMessage.count([Equal('string', 'Global No Trigger')]) == 0
|
||||
|
@ -127,24 +148,45 @@ def test_i18n_page(pub):
|
|||
# check table
|
||||
assert resp.pyquery('tr').length == TranslatableMessage.count()
|
||||
|
||||
# check form filter
|
||||
# check filters
|
||||
assert resp.form['lang'].value == 'fr'
|
||||
assert [x[2] for x in resp.form['formdef'].options] == [
|
||||
'All forms and card models',
|
||||
'test title',
|
||||
'card test',
|
||||
]
|
||||
resp.form['formdef'] = 'cards/1'
|
||||
resp = resp.form.submit()
|
||||
assert resp.pyquery('tr').length == 1
|
||||
assert {x.text for x in resp.pyquery('tr td:first-child')} == {'card test'}
|
||||
|
||||
# check filtering on a formdef/carddef outputs related workflow strings
|
||||
resp.form['formdef'] = 'forms/1'
|
||||
resp = resp.form.submit()
|
||||
assert resp.pyquery('tr').length == 14
|
||||
assert 'test title' in {x.text for x in resp.pyquery('tr td:first-child')}
|
||||
assert 'Global Manual' in {x.text for x in resp.pyquery('tr td:first-child')}
|
||||
assert 'second workflow' not in {x.text for x in resp.pyquery('tr td:first-child')}
|
||||
|
||||
resp.form['formdef'] = ''
|
||||
resp.form['q'] = 'Email'
|
||||
resp = resp.form.submit()
|
||||
assert resp.pyquery('tr').length == 2 # (email subject, email body)
|
||||
assert {x.text for x in resp.pyquery('tr td:first-child')} == {'Email body', 'Email Subject'}
|
||||
|
||||
# translate a message
|
||||
resp = resp.click('edit', index=0)
|
||||
msg = TranslatableMessage.select([Equal('string', 'Email body')])[0]
|
||||
resp = resp.click('edit', href='/%s/' % msg.id)
|
||||
resp = resp.form.submit('cancel').follow()
|
||||
resp = resp.click('edit', index=0)
|
||||
resp = resp.click('edit', href='/%s/' % msg.id)
|
||||
assert resp.pyquery('.i18n-orig-string').text() == 'Email body'
|
||||
resp.form['translation'] = 'Texte du courriel'
|
||||
resp = resp.form.submit('submit').follow()
|
||||
msg = TranslatableMessage.select([Equal('string', 'Email body')])[0]
|
||||
msg = TranslatableMessage.get(msg.id)
|
||||
assert msg.string_fr == 'Texte du courriel'
|
||||
|
||||
# go back
|
||||
resp = resp.click('edit', index=0)
|
||||
resp = resp.click('edit', href='/%s/' % msg.id)
|
||||
assert resp.form['translation'].value == 'Texte du courriel'
|
||||
resp = resp.form.submit('submit').follow()
|
||||
|
||||
|
@ -156,9 +198,6 @@ def test_i18n_page(pub):
|
|||
|
||||
def test_i18n_export(pub):
|
||||
create_superuser(pub)
|
||||
Workflow.wipe()
|
||||
BlockDef.wipe()
|
||||
TranslatableMessage.wipe()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
|
@ -210,9 +249,6 @@ def test_i18n_export(pub):
|
|||
|
||||
def test_i18n_import(pub):
|
||||
create_superuser(pub)
|
||||
Workflow.wipe()
|
||||
BlockDef.wipe()
|
||||
TranslatableMessage.wipe()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
|
@ -297,7 +333,7 @@ def test_i18n_import(pub):
|
|||
)
|
||||
resp = resp.form.submit('submit').follow()
|
||||
resp = resp.click('Go to multilinguism')
|
||||
assert resp.request.url == 'http://example.net/backoffice/i18n/?q=list&lang=fr'
|
||||
assert resp.request.url == 'http://example.net/backoffice/i18n/?q=list&formdef=&lang=fr'
|
||||
|
||||
# invalid file
|
||||
resp = app.get('/backoffice/i18n/')
|
||||
|
@ -310,9 +346,6 @@ def test_i18n_import(pub):
|
|||
|
||||
def test_i18n_pagination(pub):
|
||||
create_superuser(pub)
|
||||
Workflow.wipe()
|
||||
BlockDef.wipe()
|
||||
TranslatableMessage.wipe()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
|
@ -327,22 +360,15 @@ def test_i18n_pagination(pub):
|
|||
resp = resp.click('Go to multilinguism page')
|
||||
|
||||
# check page limit
|
||||
assert resp.pyquery('#page-links a').text() == '1 2 3 4 5 6 10 50 100'
|
||||
assert resp.pyquery('#page-links a').text() == '1 2 3 4 5 10 50 100'
|
||||
resp = resp.click('50')
|
||||
assert resp.pyquery('#page-links a').text() == '1 2 3 10 20 100'
|
||||
assert resp.pyquery('#page-links a').text() == '1 2 10 20 100'
|
||||
resp = resp.click('20')
|
||||
resp = resp.click('3')
|
||||
assert 'offset=40' in resp.request.url
|
||||
|
||||
|
||||
def test_i18n_mark_as_non_translatabe(pub):
|
||||
TranslatableMessage.wipe()
|
||||
Workflow.wipe()
|
||||
FormDef.wipe()
|
||||
BlockDef.wipe()
|
||||
Category.wipe()
|
||||
CardDef.wipe()
|
||||
MailTemplate.wipe()
|
||||
create_superuser(pub)
|
||||
workflow = Workflow(name='workflow')
|
||||
workflow.add_status('First Status')
|
||||
|
|
|
@ -284,3 +284,172 @@ def test_logged_error_trace(pub):
|
|||
resp = app.get(f'/backoffice/studio/logged-errors/{logged_error.id}/')
|
||||
assert 'pub.record_error(\'Exception' in resp.pyquery('.stack-trace--code')[0].text
|
||||
assert '\n locals:' in resp.text
|
||||
|
||||
|
||||
def test_logged_error_cleanup(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
FormDef.wipe()
|
||||
CardDef.wipe()
|
||||
Workflow.wipe()
|
||||
pub.loggederror_class.wipe()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'foo'
|
||||
formdef.store()
|
||||
carddef = CardDef()
|
||||
carddef.name = 'bar'
|
||||
carddef.store()
|
||||
workflow = Workflow()
|
||||
workflow.name = 'blah'
|
||||
workflow.store()
|
||||
|
||||
# FormDef error
|
||||
error1 = pub.loggederror_class()
|
||||
error1.summary = 'LoggedError'
|
||||
error1.formdef_class = 'FormDef'
|
||||
error1.formdef_id = formdef.id
|
||||
error1.workflow_id = workflow.id
|
||||
error1.first_occurence_timestamp = error1.latest_occurence_timestamp = datetime.datetime.now()
|
||||
error1.store()
|
||||
|
||||
# CardDef error
|
||||
error2 = pub.loggederror_class()
|
||||
error2.summary = 'LoggedError'
|
||||
error2.formdef_class = 'CardDef'
|
||||
error2.formdef_id = carddef.id
|
||||
error2.workflow_id = workflow.id
|
||||
error2.first_occurence_timestamp = error2.latest_occurence_timestamp = datetime.datetime.now()
|
||||
error2.store()
|
||||
|
||||
# workflow-only error
|
||||
error3 = pub.loggederror_class()
|
||||
error3.summary = 'LoggedError'
|
||||
error3.workflow_id = workflow.id
|
||||
error3.first_occurence_timestamp = error3.latest_occurence_timestamp = datetime.datetime.now()
|
||||
error3.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/studio/logged-errors/')
|
||||
resp = resp.click('Cleanup')
|
||||
resp = resp.form.submit('submit')
|
||||
assert pub.loggederror_class().count() == 3 # nothing removed
|
||||
|
||||
# check there's a form error if nothing is checked
|
||||
resp = app.get('/backoffice/studio/logged-errors/')
|
||||
resp = resp.click('Cleanup')
|
||||
resp.form['types$elementformdef'].checked = False
|
||||
resp.form['types$elementcarddef'].checked = False
|
||||
resp.form['types$elementothers'].checked = False
|
||||
resp = resp.form.submit('submit')
|
||||
assert resp.pyquery('[data-widget-name="types"].widget-with-error')
|
||||
|
||||
# check cleanup of only formdef errors
|
||||
error1.first_occurence_timestamp = (
|
||||
error1.latest_occurence_timestamp
|
||||
) = datetime.datetime.now() - datetime.timedelta(days=280)
|
||||
error1.store()
|
||||
error2.first_occurence_timestamp = datetime.datetime.now() - datetime.timedelta(days=120)
|
||||
error2.latest_occurence_timestamp = datetime.datetime.now() - datetime.timedelta(days=80)
|
||||
error2.store()
|
||||
error3.first_occurence_timestamp = (
|
||||
error3.latest_occurence_timestamp
|
||||
) = datetime.datetime.now() - datetime.timedelta(days=280)
|
||||
error3.store()
|
||||
resp = app.get('/backoffice/studio/logged-errors/')
|
||||
resp = resp.click('Cleanup')
|
||||
resp.form['types$elementcarddef'].checked = False
|
||||
resp.form['types$elementothers'].checked = False
|
||||
resp = resp.form.submit('submit')
|
||||
assert {x.id for x in pub.loggederror_class().select()} == {error2.id, error3.id}
|
||||
|
||||
# check cleanup latest occurence value (error2 should not be cleaned)
|
||||
resp = app.get('/backoffice/studio/logged-errors/')
|
||||
resp = resp.click('Cleanup')
|
||||
resp.form['latest_occurence'] = (datetime.datetime.now() - datetime.timedelta(days=100)).strftime(
|
||||
'%Y-%m-%d'
|
||||
)
|
||||
resp = resp.form.submit('submit')
|
||||
assert {x.id for x in pub.loggederror_class().select()} == {error2.id}
|
||||
|
||||
# check with a more recent date (error2 should be cleaned this time)
|
||||
resp = app.get('/backoffice/studio/logged-errors/')
|
||||
resp = resp.click('Cleanup')
|
||||
resp.form['latest_occurence'] = (datetime.datetime.now() - datetime.timedelta(days=10)).strftime(
|
||||
'%Y-%m-%d'
|
||||
)
|
||||
resp = resp.form.submit('submit')
|
||||
assert {x.id for x in pub.loggederror_class().select()} == set()
|
||||
|
||||
# make formdefs not accessible to current user
|
||||
pub.cfg['admin-permissions'] = {'forms': ['X']}
|
||||
pub.write_cfg()
|
||||
resp = app.get('/backoffice/studio/logged-errors/')
|
||||
resp = resp.click('Cleanup')
|
||||
assert [x.attrib['name'] for x in resp.pyquery('[type="checkbox"]')] == [
|
||||
'types$elementcarddef',
|
||||
'types$elementothers',
|
||||
]
|
||||
|
||||
|
||||
def test_logged_error_cleanup_from_filtered_page(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
FormDef.wipe()
|
||||
CardDef.wipe()
|
||||
Workflow.wipe()
|
||||
pub.loggederror_class.wipe()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'foo'
|
||||
formdef.store()
|
||||
carddef = CardDef()
|
||||
carddef.name = 'bar'
|
||||
carddef.store()
|
||||
workflow = Workflow()
|
||||
workflow.name = 'blah'
|
||||
workflow.store()
|
||||
|
||||
# FormDef error
|
||||
error1 = pub.loggederror_class()
|
||||
error1.summary = 'LoggedError'
|
||||
error1.formdef_class = 'FormDef'
|
||||
error1.formdef_id = formdef.id
|
||||
error1.first_occurence_timestamp = error1.latest_occurence_timestamp = datetime.datetime.now()
|
||||
error1.store()
|
||||
|
||||
# CardDef error
|
||||
error2 = pub.loggederror_class()
|
||||
error2.summary = 'LoggedError'
|
||||
error2.formdef_class = 'CardDef'
|
||||
error2.formdef_id = carddef.id
|
||||
error2.first_occurence_timestamp = error2.latest_occurence_timestamp = datetime.datetime.now()
|
||||
error2.store()
|
||||
|
||||
# workflow-only error
|
||||
error3 = pub.loggederror_class()
|
||||
error3.summary = 'LoggedError'
|
||||
error3.workflow_id = workflow.id
|
||||
error3.first_occurence_timestamp = error3.latest_occurence_timestamp = datetime.datetime.now()
|
||||
error3.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get(formdef.get_admin_url() + 'logged-errors/')
|
||||
resp = resp.click('Cleanup')
|
||||
resp.form['latest_occurence'] = (datetime.datetime.now() + datetime.timedelta(days=1)).strftime(
|
||||
'%Y-%m-%d'
|
||||
)
|
||||
resp = resp.form.submit('submit')
|
||||
assert not pub.loggederror_class.has_key(error1.id)
|
||||
assert pub.loggederror_class.has_key(error2.id)
|
||||
assert pub.loggederror_class.has_key(error3.id)
|
||||
|
||||
resp = app.get(workflow.get_admin_url() + 'logged-errors/')
|
||||
resp = resp.click('Cleanup')
|
||||
resp.form['latest_occurence'] = (datetime.datetime.now() + datetime.timedelta(days=1)).strftime(
|
||||
'%Y-%m-%d'
|
||||
)
|
||||
resp = resp.form.submit('submit')
|
||||
assert not pub.loggederror_class.has_key(error1.id)
|
||||
assert pub.loggederror_class.has_key(error2.id)
|
||||
assert not pub.loggederror_class.has_key(error3.id)
|
||||
|
|
|
@ -98,6 +98,8 @@ def test_settings_disabled_screens(pub):
|
|||
|
||||
resp = app.get('/backoffice/settings/emails/options')
|
||||
assert not resp.pyquery('#form_smtp_server')
|
||||
resp.form['from'] = 'test@localhost'
|
||||
resp = resp.form.submit('submit')
|
||||
|
||||
pub.site_options.set('options', 'settings-disabled-screens', '')
|
||||
pub.site_options.set('options', 'settings-hidden-screens', 'import-export')
|
||||
|
@ -397,6 +399,93 @@ def test_settings_export_import(pub):
|
|||
resp.form['file'] = Upload('export.wcs', zip_content.getvalue())
|
||||
resp = resp.form.submit('submit').follow()
|
||||
assert 'Unknown referenced objects [Unknown datasources: foobar]' in resp
|
||||
BlockDef.wipe()
|
||||
|
||||
# python expressions
|
||||
if not pub.site_options.has_section('options'):
|
||||
pub.site_options.add_section('options')
|
||||
pub.site_options.set('options', 'forbid-new-python-expressions', 'true')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'foobar'
|
||||
formdef.fields = [
|
||||
fields.PageField(id='1', label='page1', condition={'type': 'python', 'value': 'True'}),
|
||||
]
|
||||
formdef.store()
|
||||
resp = app.get('/backoffice/settings/export')
|
||||
resp = resp.form.submit('submit').follow()
|
||||
resp = resp.click('Download Export')
|
||||
zip_content = io.BytesIO(resp.body)
|
||||
resp = app.get('/backoffice/settings/import')
|
||||
resp.form['file'] = Upload('export.wcs', zip_content.getvalue())
|
||||
resp.form['confirm'].checked = True
|
||||
resp = resp.form.submit('submit').follow()
|
||||
assert 'Python expression detected' in resp
|
||||
FormDef.wipe()
|
||||
|
||||
blockdef = BlockDef()
|
||||
blockdef.name = 'foobar'
|
||||
blockdef.fields = [
|
||||
fields.StringField(id='2', label='python_prefill', prefill={'type': 'formula', 'value': '1 + 2'}),
|
||||
]
|
||||
blockdef.store()
|
||||
resp = app.get('/backoffice/settings/export')
|
||||
resp = resp.form.submit('submit').follow()
|
||||
resp = resp.click('Download Export')
|
||||
zip_content = io.BytesIO(resp.body)
|
||||
resp = app.get('/backoffice/settings/import')
|
||||
resp.form['file'] = Upload('export.wcs', zip_content.getvalue())
|
||||
resp = resp.form.submit('submit').follow()
|
||||
assert 'Python expression detected' in resp
|
||||
BlockDef.wipe()
|
||||
|
||||
workflow = Workflow(name='test')
|
||||
st0 = workflow.add_status('Status0', 'st0')
|
||||
sendsms = st0.add_action('sendsms', id='_sendsms')
|
||||
sendsms.to = 'xxx'
|
||||
sendsms.condition = {'type': 'python', 'value': 'True'}
|
||||
sendsms.parent = st0
|
||||
st0.items.append(sendsms)
|
||||
workflow.store()
|
||||
resp = app.get('/backoffice/settings/export')
|
||||
resp = resp.form.submit('submit').follow()
|
||||
resp = resp.click('Download Export')
|
||||
zip_content = io.BytesIO(resp.body)
|
||||
resp = app.get('/backoffice/settings/import')
|
||||
resp.form['file'] = Upload('export.wcs', zip_content.getvalue())
|
||||
resp.form['confirm'].checked = True
|
||||
resp = resp.form.submit('submit').follow()
|
||||
assert 'Python expression detected' in resp
|
||||
Workflow.wipe()
|
||||
|
||||
data_source = NamedDataSource(name='ds_python')
|
||||
data_source.data_source = {'type': 'formula', 'value': repr([('1', 'un'), ('2', 'deux')])}
|
||||
data_source.store()
|
||||
resp = app.get('/backoffice/settings/export')
|
||||
resp = resp.form.submit('submit').follow()
|
||||
resp = resp.click('Download Export')
|
||||
zip_content = io.BytesIO(resp.body)
|
||||
resp = app.get('/backoffice/settings/import')
|
||||
resp.form['file'] = Upload('export.wcs', zip_content.getvalue())
|
||||
resp = resp.form.submit('submit').follow()
|
||||
assert 'Python expression detected' in resp
|
||||
NamedDataSource.wipe()
|
||||
|
||||
wscall = NamedWsCall()
|
||||
wscall.name = 'Hello'
|
||||
wscall.request = {'url': 'http://example.net', 'qs_data': {'a': '=1+2'}}
|
||||
wscall.store()
|
||||
resp = app.get('/backoffice/settings/export')
|
||||
resp = resp.form.submit('submit').follow()
|
||||
resp = resp.click('Download Export')
|
||||
zip_content = io.BytesIO(resp.body)
|
||||
resp = app.get('/backoffice/settings/import')
|
||||
resp.form['file'] = Upload('export.wcs', zip_content.getvalue())
|
||||
resp = resp.form.submit('submit').follow()
|
||||
assert 'Python expression detected' in resp
|
||||
NamedWsCall.wipe()
|
||||
|
||||
# check a backup of settings has been created
|
||||
assert [x for x in os.listdir(pub.app_dir) if x.startswith('config.pck.backup-')]
|
||||
|
@ -1082,17 +1171,31 @@ def test_i18n(pub):
|
|||
def test_submission_channels(pub):
|
||||
create_superuser(pub)
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/settings/submission-channels')
|
||||
resp = app.get('/backoffice/settings/backoffice-submission')
|
||||
resp.form['include-in-global-listing'].checked = True
|
||||
resp = resp.form.submit('submit')
|
||||
|
||||
pub.reload_cfg()
|
||||
assert pub.cfg['submission-channels']['include-in-global-listing'] is True
|
||||
|
||||
resp = app.get('/backoffice/settings/submission-channels')
|
||||
resp = app.get('/backoffice/settings/backoffice-submission')
|
||||
assert resp.form['include-in-global-listing'].checked
|
||||
|
||||
|
||||
def test_backoffice_submission(pub):
|
||||
create_superuser(pub)
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/settings/backoffice-submission')
|
||||
resp.form['redirect'] = 'https://example.net'
|
||||
resp = resp.form.submit('submit')
|
||||
|
||||
pub.reload_cfg()
|
||||
assert pub.cfg['backoffice-submission']['redirect'] == 'https://example.net'
|
||||
|
||||
resp = app.get('/backoffice/settings/backoffice-submission')
|
||||
assert resp.form['redirect'].value == 'https://example.net'
|
||||
|
||||
|
||||
def test_hobo_locked_settings(pub):
|
||||
create_superuser(pub)
|
||||
app = login(get_app(pub))
|
||||
|
|
|
@ -133,6 +133,7 @@ def test_studio_home_recent_changes(pub):
|
|||
other_user = pub.user_class(name='other')
|
||||
other_user.store()
|
||||
|
||||
pub.snapshot_class.wipe()
|
||||
BlockDef.wipe()
|
||||
CardDef.wipe()
|
||||
NamedDataSource.wipe()
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
import datetime
|
||||
import json
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from django.utils.html import escape
|
||||
from django.utils.timezone import make_aware
|
||||
from webtest import Upload
|
||||
|
||||
from wcs import fields
|
||||
from wcs import fields, workflow_tests
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.qommon.upload_storage import PicklableUpload
|
||||
from wcs.testdef import TestDef, TestResult
|
||||
from wcs.testdef import TestDef, TestResult, WebserviceResponse
|
||||
from wcs.workflow_tests import WorkflowTests
|
||||
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef
|
||||
from wcs.wscalls import NamedWsCall
|
||||
|
||||
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
|
||||
from .test_all import create_superuser
|
||||
|
@ -30,6 +33,8 @@ def pub():
|
|||
FormDef.wipe()
|
||||
TestDef.wipe()
|
||||
TestResult.wipe()
|
||||
WorkflowTests.wipe()
|
||||
WebserviceResponse.wipe()
|
||||
return pub
|
||||
|
||||
|
||||
|
@ -38,7 +43,7 @@ def teardown_module(module):
|
|||
|
||||
|
||||
def test_tests_page(pub):
|
||||
create_superuser(pub)
|
||||
user = create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
|
@ -54,7 +59,12 @@ def test_tests_page(pub):
|
|||
|
||||
resp = resp.click('New')
|
||||
resp.form['name'] = 'First test'
|
||||
resp = resp.form.submit().follow()
|
||||
resp = resp.form.submit()
|
||||
|
||||
testdef = TestDef.select()[0]
|
||||
assert testdef.agent_id == str(user.id)
|
||||
|
||||
resp = resp.follow()
|
||||
assert 'Edit test data' in resp.text
|
||||
|
||||
resp.form['f1'] = 'abcdefg'
|
||||
|
@ -86,6 +96,25 @@ def test_tests_page(pub):
|
|||
app.get('/backoffice/forms/1/tests/results/run').follow()
|
||||
|
||||
|
||||
def test_tests_page_breadcrumb(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'Short title'
|
||||
formdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get(formdef.get_admin_url() + 'tests/')
|
||||
assert 'Short title' in resp.text
|
||||
|
||||
formdef.name = 'This is a long title'
|
||||
formdef.store()
|
||||
|
||||
resp = app.get(formdef.get_admin_url() + 'tests/')
|
||||
assert 'This is a long…' in resp.text
|
||||
|
||||
|
||||
def test_tests_page_creation_from_formdata(pub):
|
||||
user = create_superuser(pub)
|
||||
|
||||
|
@ -103,7 +132,7 @@ def test_tests_page_creation_from_formdata(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = 'abcdefg'
|
||||
formdata.user_id = user.id
|
||||
formdata.store()
|
||||
|
@ -118,11 +147,12 @@ def test_tests_page_creation_from_formdata(pub):
|
|||
|
||||
testdef = TestDef.select()[0]
|
||||
assert testdef.data['user']['id'] == 1
|
||||
assert testdef.agent_id == str(user.id)
|
||||
assert not testdef.is_in_backoffice
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2022, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2022, 1, 1, 0, 0))
|
||||
formdata.data['1'] = 'hijklmn'
|
||||
formdata.backoffice_submission = True
|
||||
formdata.store()
|
||||
|
@ -167,51 +197,74 @@ def test_tests_import_export(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = 'a'
|
||||
formdata.user_id = user.id
|
||||
formdata.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.ButtonClick(id='1', button_name='Go to end status'),
|
||||
workflow_tests.AssertStatus(id='2', status_name='End status'),
|
||||
]
|
||||
testdef.name = 'First test'
|
||||
testdef.store()
|
||||
|
||||
response = WebserviceResponse()
|
||||
response.testdef_id = testdef.id
|
||||
response.name = 'Response xxx'
|
||||
response.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/' % testdef.id)
|
||||
export_resp = resp.click('Export')
|
||||
assert 'filename=test-first-test.wcs' in export_resp.headers['content-disposition']
|
||||
|
||||
resp = resp.click('Delete')
|
||||
resp = resp.form.submit().follow()
|
||||
assert 'First test' not in resp.text
|
||||
assert WorkflowTests.count() == 0
|
||||
assert WebserviceResponse.count() == 0
|
||||
|
||||
resp = resp.click('Import')
|
||||
resp.form['file'] = Upload('export.json', export_resp.body, 'application/json')
|
||||
resp.form['file'] = Upload('export.wcs', export_resp.body)
|
||||
resp = resp.form.submit().follow()
|
||||
assert TestDef.count() == 1
|
||||
assert WorkflowTests.count() == 1
|
||||
assert WebserviceResponse.count() == 1
|
||||
assert 'First test' in resp.text
|
||||
assert escape('Test "First test" has been successfully imported.') in resp.text
|
||||
|
||||
imported_testdef = TestDef.select()[0]
|
||||
assert imported_testdef.export_to_json() == testdef.export_to_json()
|
||||
|
||||
export_json = json.loads(export_resp.body)
|
||||
export_json['data']['fields']['1'] = 'b'
|
||||
assert imported_testdef.name == testdef.name
|
||||
assert imported_testdef.data == testdef.data
|
||||
|
||||
resp = resp.click('Import')
|
||||
resp.form['file'] = Upload('export.json', json.dumps(export_json).encode(), 'application/json')
|
||||
resp.form['file'] = Upload('export.wcs', export_resp.body)
|
||||
resp = resp.form.submit().follow()
|
||||
assert TestDef.count() == 1
|
||||
assert 'First test' in resp.text
|
||||
assert TestDef.count() == 2
|
||||
assert WorkflowTests.count() == 2
|
||||
assert WebserviceResponse.count() == 2
|
||||
assert len(resp.pyquery('li a:contains("First test")')) == 2
|
||||
assert escape('Test "First test" has been successfully imported.') in resp.text
|
||||
|
||||
imported_testdef = TestDef.get(imported_testdef.id)
|
||||
assert imported_testdef.data['fields']['1'] == 'b'
|
||||
|
||||
resp = resp.click('Import')
|
||||
resp.form['file'] = Upload('export.json', b'invalid', 'application/json')
|
||||
resp.form['file'] = Upload('export.wcs', b'invalid')
|
||||
resp = resp.form.submit()
|
||||
assert 'Expecting value: line 1 column 1' in resp.text
|
||||
assert 'Invalid File' in resp.text
|
||||
|
||||
formdef2 = FormDef()
|
||||
formdef2.name = 'test title'
|
||||
formdef2.store()
|
||||
|
||||
resp = app.get('/backoffice/forms/%s/tests/' % formdef2.id)
|
||||
resp = resp.click('Import')
|
||||
|
||||
resp.form['file'] = Upload('export.wcs', export_resp.body)
|
||||
resp = resp.form.submit().follow()
|
||||
assert len(TestDef.select_for_objectdef(formdef2)) == 1
|
||||
assert len(resp.pyquery('li a:contains("First test")')) == 1
|
||||
|
||||
|
||||
def test_tests_status_page(pub):
|
||||
|
@ -224,7 +277,7 @@ def test_tests_status_page(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = 'This is a test'
|
||||
formdata.user_id = user.id
|
||||
formdata.store()
|
||||
|
@ -264,7 +317,7 @@ def test_tests_status_page_block_field(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = {'data': [{'1': 'foo'}]}
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
|
@ -288,7 +341,7 @@ def test_tests_status_page_image_field(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
|
||||
upload = PicklableUpload('test.jpeg', 'image/jpeg')
|
||||
with open(os.path.join(os.path.dirname(__file__), '..', 'image-with-gps-data.jpeg'), 'rb') as jpg:
|
||||
|
@ -325,7 +378,7 @@ def test_tests_edit(pub):
|
|||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.user_id = user.id
|
||||
formdata.data = {'1': 'xxx'}
|
||||
formdata.store()
|
||||
|
@ -375,7 +428,7 @@ def test_tests_edit_data(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = 'test 1'
|
||||
formdata.data['3'] = 'test 2'
|
||||
formdata.user_id = user.id
|
||||
|
@ -434,7 +487,7 @@ def test_tests_edit_data_mark_as_failing(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = '12345'
|
||||
formdata.store()
|
||||
|
||||
|
@ -516,7 +569,7 @@ def test_tests_edit_data_mark_as_failing_hidden_error(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['0'] = 'not-digits'
|
||||
formdata.data['1'] = 'also-not-digits'
|
||||
formdata.store()
|
||||
|
@ -548,7 +601,7 @@ def test_tests_edit_data_mark_as_failing_required_field(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
|
@ -599,7 +652,7 @@ def test_tests_edit_data_is_in_backoffice(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = '12345'
|
||||
formdata.store()
|
||||
|
||||
|
@ -690,7 +743,7 @@ def test_tests_manual_run(pub):
|
|||
# create test
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = 'a'
|
||||
formdata.user_id = user.id
|
||||
formdata.store()
|
||||
|
@ -710,7 +763,7 @@ def test_tests_manual_run(pub):
|
|||
assert 'Started by: Manual run.' in resp.text
|
||||
assert len(resp.pyquery('tr')) == 1
|
||||
assert 'Success!' in resp.text
|
||||
assert 'Missing required fields: 0' in resp.text
|
||||
assert 'Display details' not in resp.text
|
||||
|
||||
resp = resp.click('First test')
|
||||
assert 'Edit data' in resp.text
|
||||
|
@ -722,7 +775,7 @@ def test_tests_manual_run(pub):
|
|||
assert len(resp.pyquery('span.test-failure')) == 0
|
||||
|
||||
# add required field
|
||||
formdef.fields.append(fields.StringField(id='2', label='String', varname='string'))
|
||||
formdef.fields.append(fields.StringField(id='2', label='String field', varname='string'))
|
||||
formdef.store()
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/') # run from test listing page
|
||||
|
@ -737,7 +790,9 @@ def test_tests_manual_run(pub):
|
|||
resp = resp.click('#%s' % result.id)
|
||||
assert 'Started by: Manual run.' in resp.text
|
||||
assert 'Success!' in resp.text
|
||||
assert 'Missing required fields: 1' in resp.text
|
||||
|
||||
resp = resp.click('Display details')
|
||||
assert 'String field' in resp.text
|
||||
|
||||
# add validation to first field
|
||||
formdef.fields[0].validation = {'type': 'digits'}
|
||||
|
@ -759,6 +814,10 @@ def test_tests_manual_run(pub):
|
|||
assert 'You should enter digits only, for example: 123.' in resp.text
|
||||
assert 'disabled' not in resp.text
|
||||
|
||||
resp = resp.click('Run tests again')
|
||||
resp = app.get('/backoffice/forms/1/tests/results/')
|
||||
assert len(resp.pyquery('tr')) == 4
|
||||
|
||||
TestDef.remove_object(testdef.id)
|
||||
resp = app.get('/backoffice/forms/1/tests/results/%s/' % result.id)
|
||||
assert 'disabled' in resp.text
|
||||
|
@ -767,6 +826,287 @@ def test_tests_manual_run(pub):
|
|||
app.get('/backoffice/forms/1/tests/results/42/', status=404)
|
||||
|
||||
|
||||
def test_tests_result_recorded_errors(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.fields = [
|
||||
fields.ComputedField(
|
||||
id='1',
|
||||
label='Computed',
|
||||
varname='computed',
|
||||
value_template='{{ forms|objects:"test-title"|filter_by:"unknown"|filter_value:"xxx"|count }}',
|
||||
freeze_on_initial_value=True,
|
||||
),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.name = 'First test'
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/forms/1/tests/results/run').follow()
|
||||
resp = resp.click('Display details')
|
||||
|
||||
assert 'Missing required fields' not in resp.text
|
||||
assert 'Recorded errors:' in resp.text
|
||||
assert escape('Invalid filter "unknown"') in resp.text
|
||||
|
||||
|
||||
def test_tests_result_sent_requests(pub, http_requests):
|
||||
create_superuser(pub)
|
||||
|
||||
wscall = NamedWsCall()
|
||||
wscall.name = 'Hello world'
|
||||
wscall.request = {'url': 'http://remote.example.net/json'}
|
||||
wscall.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.fields = [
|
||||
fields.PageField(
|
||||
id='0',
|
||||
label='1st page',
|
||||
post_conditions=[
|
||||
{
|
||||
'condition': {'type': 'django', 'value': 'form_var_computed_foo == "bar"'},
|
||||
'error_message': '',
|
||||
}
|
||||
],
|
||||
),
|
||||
fields.ComputedField(
|
||||
id='1',
|
||||
label='Computed',
|
||||
varname='computed',
|
||||
value_template='{{ webservice.hello_world }}',
|
||||
freeze_on_initial_value=True,
|
||||
),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.name = 'First test'
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/forms/1/tests/results/run').follow()
|
||||
|
||||
assert 'Success!' in resp.text
|
||||
assert http_requests.count() == 1
|
||||
http_requests.empty()
|
||||
|
||||
resp = resp.click('Display details')
|
||||
|
||||
assert 'Sent requests:' in resp.text
|
||||
assert 'GET http://remote.example.net/json' in resp.text
|
||||
assert 'Used webservice response:' not in resp.text
|
||||
|
||||
response = WebserviceResponse()
|
||||
response.testdef_id = testdef.id
|
||||
response.name = 'Response xxx'
|
||||
response.url = 'http://remote.example.net/json'
|
||||
response.payload = '{"foo": "wrong"}'
|
||||
response.store()
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/results/run').follow()
|
||||
|
||||
assert 'Success!' not in resp.text
|
||||
assert http_requests.count() == 0
|
||||
|
||||
resp = resp.click('Display details')
|
||||
result_url = resp.request.url
|
||||
|
||||
assert 'Sent requests:' in resp.text
|
||||
assert 'GET http://remote.example.net/json' in resp.text
|
||||
assert 'Used webservice response:' in resp.text
|
||||
|
||||
resp = resp.click('Response xxx')
|
||||
assert 'Edit webservice response' in resp.text
|
||||
|
||||
response.remove_self()
|
||||
resp = app.get(result_url)
|
||||
|
||||
assert 'Used webservice response:' in resp.text
|
||||
assert 'Response xxx' not in resp.text
|
||||
assert 'deleted' in resp.text
|
||||
|
||||
wscall.request['method'] = 'POST'
|
||||
wscall.store()
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/results/run').follow()
|
||||
|
||||
assert 'Success!' not in resp.text
|
||||
assert http_requests.count() == 0
|
||||
|
||||
resp = resp.click('Display details')
|
||||
|
||||
assert 'Sent requests:' in resp.text
|
||||
assert 'POST http://remote.example.net/json' in resp.text
|
||||
assert 'Request was blocked since it is not a GET request.' in resp.text
|
||||
assert 'Recorded errors:' in resp.text
|
||||
assert 'error in HTTP request to remote.example.net (method must be GET)' in resp.text
|
||||
|
||||
resp = resp.click('You can create corresponding webservice response here.')
|
||||
assert 'Webservice responses' in resp.text
|
||||
|
||||
|
||||
def test_tests_result_error_field(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.fields = [
|
||||
fields.StringField(id='0', label='Text Field', varname='text', validation={'type': 'digits'}),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.data['0'] = 'not-digits'
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.name = 'First test'
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/forms/1/tests/results/run')
|
||||
result_url = resp.location
|
||||
resp = resp.follow()
|
||||
|
||||
assert escape('Invalid value "not-digits" for field "Text Field"') in resp.text
|
||||
|
||||
resp = resp.click('Display details')
|
||||
|
||||
assert 'Field linked to error:' in resp.text
|
||||
assert 'deleted' not in resp.text
|
||||
|
||||
resp = resp.click('Text Field')
|
||||
|
||||
assert resp.pyquery('h2').text() == 'Text Field'
|
||||
|
||||
formdef.fields = []
|
||||
formdef.store()
|
||||
|
||||
resp = app.get(result_url)
|
||||
resp = resp.click('Display details')
|
||||
|
||||
assert 'Text Field' not in resp.text
|
||||
assert 'deleted' in resp.text
|
||||
|
||||
|
||||
def test_tests_result_inspect(pub):
|
||||
user = create_superuser(pub)
|
||||
|
||||
role = pub.role_class(name='test role')
|
||||
role.store()
|
||||
user.roles = [role.id]
|
||||
user.store()
|
||||
|
||||
workflow = Workflow(name='Workflow One')
|
||||
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
|
||||
workflow.backoffice_fields_formdef.fields = [
|
||||
fields.StringField(id='1', label='Text BO', varname='text_bo'),
|
||||
]
|
||||
|
||||
new_status = workflow.add_status(name='New status')
|
||||
set_backoffice_fields = new_status.add_action('set-backoffice-fields')
|
||||
set_backoffice_fields.fields = [{'field_id': '1', 'value': 'goodbye'}]
|
||||
|
||||
jump = new_status.add_action('choice')
|
||||
jump.label = 'Loop on status'
|
||||
jump.status = new_status.id
|
||||
jump.by = [role.id]
|
||||
|
||||
wscall = new_status.add_action('webservice_call')
|
||||
wscall.url = 'http://example.com/json'
|
||||
wscall.varname = 'test_webservice'
|
||||
wscall.qs_data = {'a': 'b'}
|
||||
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.fields = [
|
||||
fields.StringField(id='0', label='Text Field', varname='text'),
|
||||
]
|
||||
formdef.workflow = workflow
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.data['0'] = 'hello'
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.name = 'First test'
|
||||
testdef.agent_id = user.id
|
||||
testdef.is_in_backoffice = True
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.ButtonClick(id='1', button_name='Loop on status'),
|
||||
]
|
||||
testdef.store()
|
||||
|
||||
response = WebserviceResponse()
|
||||
response.testdef_id = testdef.id
|
||||
response.name = 'Fake response'
|
||||
response.url = 'http://example.com/json'
|
||||
response.payload = '{"foo": "bar"}'
|
||||
response.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/forms/1/tests/results/run')
|
||||
result_url = resp.location
|
||||
resp = resp.follow()
|
||||
resp = resp.click('Display inspect')
|
||||
|
||||
assert 'form_var_text' in resp.text
|
||||
assert 'form_var_text_bo' in resp.text
|
||||
assert 'form_workflow_data_test_webservice_response_foo' in resp.text
|
||||
|
||||
assert [x.text_content() for x in resp.pyquery('div#inspect-timeline a')] == [
|
||||
'New status',
|
||||
'Backoffice Data',
|
||||
'Webservice',
|
||||
'Action button - Manual Jump Loop on status',
|
||||
'Backoffice Data',
|
||||
'Webservice',
|
||||
]
|
||||
|
||||
resp.form['django-condition'] = 'form_var_text == "hello"'
|
||||
resp = resp.form.submit()
|
||||
assert 'Condition result' in resp.text
|
||||
assert 'result-true' in resp.text
|
||||
|
||||
resp.form['django-condition'] = 'form_var_text_bo == "goodbye"'
|
||||
resp = resp.form.submit()
|
||||
assert 'Condition result' in resp.text
|
||||
assert 'result-true' in resp.text
|
||||
|
||||
resp.form['django-condition'] = 'form_submission_backoffice'
|
||||
resp = resp.form.submit()
|
||||
assert 'Condition result' in resp.text
|
||||
assert 'result-true' in resp.text
|
||||
|
||||
# check inspect is not accessible for old results
|
||||
light_test_result = TestResult.select()[-1]
|
||||
test_result = TestResult.get(light_test_result.id)
|
||||
del test_result.results[0]['formdata']
|
||||
test_result.store()
|
||||
|
||||
resp = app.get(result_url)
|
||||
assert 'Display inspect' not in resp.text
|
||||
|
||||
|
||||
def test_tests_run_order(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
|
@ -809,7 +1149,73 @@ def test_tests_duplicate(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = 'abcdefg'
|
||||
formdata.user_id = user.id
|
||||
formdata.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.name = 'First test'
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.ButtonClick(id='1', button_name='Go to end status'),
|
||||
workflow_tests.AssertStatus(id='2', status_name='End status'),
|
||||
]
|
||||
testdef.store()
|
||||
|
||||
response = WebserviceResponse()
|
||||
response.testdef_id = testdef.id
|
||||
response.name = 'Response xxx'
|
||||
response.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
assert TestDef.count() == 1
|
||||
assert WorkflowTests.count() == 1
|
||||
assert WebserviceResponse.count() == 1
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/' % testdef.id)
|
||||
resp = resp.click('Duplicate')
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert 'First test (copy)' in resp.text
|
||||
assert 'abcdefg' in resp.text
|
||||
assert TestDef.count() == 2
|
||||
assert WorkflowTests.count() == 2
|
||||
assert WebserviceResponse.count() == 2
|
||||
|
||||
testdef1, testdef2 = TestDef.select(order_by='id')
|
||||
testdef1.workflow_tests.actions[0].button_name = 'Changed'
|
||||
testdef1.store()
|
||||
|
||||
response = testdef1.get_webservice_responses()[0]
|
||||
response.name = 'Changed'
|
||||
response.store()
|
||||
|
||||
testdef1, testdef2 = TestDef.select(order_by='id')
|
||||
assert testdef1.workflow_tests.actions[0].button_name == 'Changed'
|
||||
assert testdef2.workflow_tests.actions[0].button_name == 'Go to end status'
|
||||
assert testdef1.get_webservice_responses()[0].name == 'Changed'
|
||||
assert testdef2.get_webservice_responses()[0].name == 'Response xxx'
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/' % testdef.id)
|
||||
resp = resp.click('Duplicate')
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert 'First test (copy 2)' in resp.text
|
||||
assert 'abcdefg' in resp.text
|
||||
assert TestDef.count() == 3
|
||||
|
||||
|
||||
def test_form_with_test_duplicate(pub):
|
||||
user = create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.fields = [fields.StringField(id='1', varname='test field', label='Test')]
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.data['1'] = 'abcdefg'
|
||||
formdata.user_id = user.id
|
||||
formdata.store()
|
||||
|
@ -819,24 +1225,10 @@ def test_tests_duplicate(pub):
|
|||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
assert TestDef.count() == 1
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/' % testdef.id)
|
||||
resp = app.get('/backoffice/forms/1/')
|
||||
resp = resp.click('Duplicate')
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert 'First test (copy)' in resp.text
|
||||
assert 'abcdefg' in resp.text
|
||||
assert TestDef.count() == 2
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/' % testdef.id)
|
||||
resp = resp.click('Duplicate')
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert 'First test (copy 2)' in resp.text
|
||||
assert 'abcdefg' in resp.text
|
||||
assert TestDef.count() == 3
|
||||
assert resp.pyquery('#appbar h2').text() == 'test title (copy)'
|
||||
|
||||
|
||||
def test_tests_page_with_empty_map_field(pub):
|
||||
|
@ -849,7 +1241,7 @@ def test_tests_page_with_empty_map_field(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = None
|
||||
formdata.store()
|
||||
|
||||
|
@ -934,12 +1326,12 @@ def test_tests_exclude_self(pub):
|
|||
|
||||
submitted_formdata = formdef.data_class()()
|
||||
submitted_formdata.just_created()
|
||||
submitted_formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
submitted_formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
submitted_formdata.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/forms/1/tests/new')
|
||||
|
@ -949,3 +1341,85 @@ def test_tests_exclude_self(pub):
|
|||
|
||||
resp = resp.form.submit('submit').follow()
|
||||
assert 'First test' in resp.text
|
||||
|
||||
|
||||
def test_tests_webservice_response(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.name = 'First test'
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/' % testdef.id)
|
||||
|
||||
resp = resp.click('Webservice response')
|
||||
assert 'There are no webservice responses yet.' in resp.text
|
||||
|
||||
resp = resp.click('New')
|
||||
resp.form['name'] = 'Test response'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
resp = resp.form.submit('submit')
|
||||
assert resp.pyquery('.error').text() == 'required field required field'
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/webservice-responses/' % testdef.id)
|
||||
assert 'Test response' in resp.text
|
||||
assert 'There are no webservice responses yet.' not in resp.text
|
||||
assert '(not configured)' in resp.text
|
||||
|
||||
resp = resp.click('Test response')
|
||||
resp.form['url'] = 'http://example.com/'
|
||||
resp.form['payload'] = '{"a": "b"}'
|
||||
resp.form['status_code'] = '400'
|
||||
resp.form['qs_data$element0key'] = 'foo'
|
||||
resp.form['method'] = 'POST (JSON)'
|
||||
resp.form['post_data$element0key'] = 'bar'
|
||||
resp = resp.form.submit('submit').follow()
|
||||
|
||||
assert 'Test response' in resp.text
|
||||
assert '(not configured)' not in resp.text
|
||||
|
||||
response = testdef.get_webservice_responses()[0]
|
||||
assert response.name == 'Test response'
|
||||
assert response.url == 'http://example.com/'
|
||||
assert response.payload == '{"a": "b"}'
|
||||
assert response.status_code == 400
|
||||
assert response.qs_data == {'foo': ''}
|
||||
assert response.method == 'POST'
|
||||
assert response.post_data == {'bar': ''}
|
||||
|
||||
resp = resp.click('Duplicate').follow()
|
||||
assert 'Test response' in resp.text
|
||||
assert 'not configured' not in resp.text
|
||||
assert 'Test response (copy)' in resp.text
|
||||
|
||||
response = testdef.get_webservice_responses()[1]
|
||||
assert response.name == 'Test response (copy)'
|
||||
assert response.url == 'http://example.com/'
|
||||
assert response.payload == '{"a": "b"}'
|
||||
|
||||
resp = resp.click('Remove', href=response.id)
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert 'Test response (copy)' not in resp.text
|
||||
|
||||
resp = resp.click('Test response')
|
||||
resp.form['payload'] = '{"a"}'
|
||||
resp = resp.form.submit()
|
||||
|
||||
assert "Invalid JSON: Expecting ':' delimiter: line 1 column 5 (char 4)" in resp.text
|
||||
|
||||
resp.form['url'] = 'xxx'
|
||||
resp.form['payload'] = '{}'
|
||||
resp = resp.form.submit()
|
||||
|
||||
assert 'must start with http://' in resp.text
|
||||
|
|
|
@ -75,6 +75,57 @@ def test_workflows_default(pub):
|
|||
assert 'Delete' not in resp.text
|
||||
|
||||
|
||||
def test_workflows_status_icons(pub):
|
||||
Workflow.wipe()
|
||||
workflow = Workflow(name='foo')
|
||||
workflow.possible_status = Workflow.get_default_workflow().possible_status[:]
|
||||
workflow.store()
|
||||
|
||||
create_superuser(pub)
|
||||
app = login(get_app(pub))
|
||||
|
||||
# check status type icons/labels
|
||||
resp = app.get('/backoffice/workflows/1/')
|
||||
assert [
|
||||
(PyQuery(x).text(), re.search(r'status-type-[a-z]+', x.attrib['class']).group(0))
|
||||
for x in resp.pyquery('#status-list li')
|
||||
] == [
|
||||
('Just Submitted (transition status)', 'status-type-transition'),
|
||||
('New (pause status)', 'status-type-waitpoint'),
|
||||
('Rejected (final status)', 'status-type-endpoint'),
|
||||
('Accepted (pause status)', 'status-type-waitpoint'),
|
||||
('Finished (final status)', 'status-type-endpoint'),
|
||||
]
|
||||
|
||||
workflow.possible_status[1].loop_items_template = 'foobar'
|
||||
workflow.store()
|
||||
resp = app.get('/backoffice/workflows/1/')
|
||||
assert [
|
||||
(PyQuery(x).text(), re.search(r'status-type-[a-z]+', x.attrib['class']).group(0))
|
||||
for x in resp.pyquery('#status-list li')
|
||||
] == [
|
||||
('Just Submitted (transition status)', 'status-type-transition'),
|
||||
('New (pause status)', 'status-type-waitpoint'),
|
||||
('Rejected (final status)', 'status-type-endpoint'),
|
||||
('Accepted (pause status)', 'status-type-waitpoint'),
|
||||
('Finished (final status)', 'status-type-endpoint'),
|
||||
]
|
||||
|
||||
workflow.possible_status[1].after_loop_status = str(workflow.possible_status[3].id)
|
||||
workflow.store()
|
||||
resp = app.get('/backoffice/workflows/1/')
|
||||
assert [
|
||||
(PyQuery(x).text(), re.search(r'status-type-[a-z]+', x.attrib['class']).group(0))
|
||||
for x in resp.pyquery('#status-list li')
|
||||
] == [
|
||||
('Just Submitted (transition status)', 'status-type-transition'),
|
||||
('New (transition status)', 'status-type-transition'),
|
||||
('Rejected (final status)', 'status-type-endpoint'),
|
||||
('Accepted (pause status)', 'status-type-waitpoint'),
|
||||
('Finished (final status)', 'status-type-endpoint'),
|
||||
]
|
||||
|
||||
|
||||
def test_workflows_new(pub):
|
||||
create_superuser(pub)
|
||||
Workflow.wipe()
|
||||
|
@ -161,6 +212,7 @@ def test_workflows_svg(pub):
|
|||
|
||||
workflow = Workflow(name='foo')
|
||||
st1 = workflow.add_status(name='baz')
|
||||
st2 = workflow.add_status(name='bar')
|
||||
commentable = st1.add_action('commentable', id='_commentable')
|
||||
commentable.by = [role.id]
|
||||
commentable.label = 'foobar'
|
||||
|
@ -175,6 +227,15 @@ def test_workflows_svg(pub):
|
|||
assert resp.content_type == 'image/svg+xml'
|
||||
assert '/static/css/dc2/admin.css' in resp.text
|
||||
|
||||
assert '>baz<' in resp
|
||||
assert 'Jump after loop' not in resp
|
||||
st1.loop_items_template = '{{ "abc"|make_list }}'
|
||||
st1.after_loop_status = str(st2.id)
|
||||
workflow.store()
|
||||
resp = app.get('/backoffice/workflows/%s/status/%s/svg' % (workflow.id, st1.id))
|
||||
assert '>baz (With loop)<' in resp
|
||||
assert 'Jump after loop' in resp
|
||||
|
||||
|
||||
def test_workflow_user_roles_svg(pub):
|
||||
create_superuser(pub)
|
||||
|
@ -351,6 +412,7 @@ def test_workflows_edit_status(pub):
|
|||
Workflow.wipe()
|
||||
workflow = Workflow(name='foo')
|
||||
workflow.add_status(name='baz')
|
||||
st2 = workflow.add_status(name='bar')
|
||||
workflow.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
@ -421,6 +483,31 @@ def test_workflows_edit_status(pub):
|
|||
resp = resp.follow()
|
||||
assert 'Hello' in Workflow.get(1).possible_status[0].backoffice_info_text
|
||||
|
||||
assert Workflow.get(1).possible_status[0].loop_items_template is None
|
||||
resp = resp.click('Options')
|
||||
assert 'loop_items_template' not in resp
|
||||
assert 'after_loop_status' not in resp
|
||||
|
||||
pub.site_options.set('options', 'workflow-status-loop', 'true')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
resp = app.get('/backoffice/workflows/1/status/1/options')
|
||||
resp.forms[0]['loop_items_template$value_template'].value = '{{ "abc"|make_list }}'
|
||||
resp.forms[0]['after_loop_status'].value = st2.id
|
||||
resp = resp.forms[0].submit()
|
||||
assert resp.location == 'http://example.net/backoffice/workflows/1/status/1/'
|
||||
resp = resp.follow()
|
||||
assert Workflow.get(1).possible_status[0].loop_items_template == '{{ "abc"|make_list }}'
|
||||
assert Workflow.get(1).possible_status[0].after_loop_status == str(st2.id)
|
||||
|
||||
resp = resp.click('Options')
|
||||
assert resp.forms[0]['loop_items_template$value_template'].value == '{{ "abc"|make_list }}'
|
||||
resp.forms[0]['loop_items_template$value_template'].value = ''
|
||||
resp = resp.forms[0].submit()
|
||||
assert resp.location == 'http://example.net/backoffice/workflows/1/status/1/'
|
||||
resp = resp.follow()
|
||||
assert Workflow.get(1).possible_status[0].loop_items_template is None
|
||||
|
||||
|
||||
def test_workflows_delete_status(pub):
|
||||
create_superuser(pub)
|
||||
|
@ -903,6 +990,28 @@ def test_workflows_export_import(pub):
|
|||
assert 'Invalid File' in resp.text
|
||||
assert Workflow.count() == 2
|
||||
|
||||
# python expression
|
||||
if not pub.site_options.has_section('options'):
|
||||
pub.site_options.add_section('options')
|
||||
pub.site_options.set('options', 'forbid-new-python-expressions', 'true')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
st0 = workflow.add_status('Status0', 'st0')
|
||||
sendsms = st0.add_action('sendsms', id='_sendsms')
|
||||
sendsms.to = 'xxx'
|
||||
sendsms.condition = {'type': 'python', 'value': 'True'}
|
||||
sendsms.parent = st0
|
||||
st0.items.append(sendsms)
|
||||
workflow.store()
|
||||
resp = app.get('/backoffice/workflows/%s/' % workflow.id)
|
||||
resp = resp.click(href=re.compile('^export$'))
|
||||
xml_export = resp.text
|
||||
resp = app.get('/backoffice/workflows/')
|
||||
resp = resp.click('Import')
|
||||
resp.form['file'] = Upload('wf.wcs', xml_export.encode('utf-8'))
|
||||
resp = resp.form.submit('submit')
|
||||
assert 'Python expression detected' in resp
|
||||
|
||||
|
||||
def test_workflows_import_from_url(pub):
|
||||
create_superuser(pub)
|
||||
|
@ -2353,6 +2462,29 @@ def test_workflows_backoffice_fields_backlinks_to_actions(pub):
|
|||
}
|
||||
|
||||
|
||||
def test_workflows_backoffice_fields_with_same_label(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
Workflow.wipe()
|
||||
workflow = Workflow(name='foo')
|
||||
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
|
||||
workflow.backoffice_fields_formdef.fields = [
|
||||
fields.StringField(id='bo1', varname='bo1', label='variable'),
|
||||
fields.StringField(id='bo2', varname='bo2', label='variable'),
|
||||
]
|
||||
status = workflow.add_status(name='baz')
|
||||
action1 = status.add_action('set-backoffice-fields')
|
||||
workflow.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get(action1.get_admin_url())
|
||||
assert resp.form['fields$element0$field_id'].options == [
|
||||
('', False, ''),
|
||||
('bo1', False, 'variable - Text (line) (bo1)'),
|
||||
('bo2', False, 'variable - Text (line) (bo2)'),
|
||||
]
|
||||
|
||||
|
||||
def test_workflows_fields_labels(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
|
@ -2906,6 +3038,8 @@ def test_workflows_external_workflow_action_config(pub):
|
|||
trigger = action.append_trigger('webservice')
|
||||
trigger.identifier = 'test'
|
||||
action.add_action('remove')
|
||||
trigger = action.append_trigger('webservice')
|
||||
trigger.identifier = 'test2'
|
||||
external_wf.store()
|
||||
|
||||
FormDef.wipe()
|
||||
|
@ -2914,6 +3048,11 @@ def test_workflows_external_workflow_action_config(pub):
|
|||
formdef.workflow = external_wf
|
||||
formdef.store()
|
||||
|
||||
formdef2 = FormDef()
|
||||
formdef2.name = 'other'
|
||||
formdef2.workflow = external_wf
|
||||
formdef2.store()
|
||||
|
||||
wf = Workflow(name='foo')
|
||||
st = wf.add_status('New')
|
||||
st.add_action('external_workflow_global_action')
|
||||
|
@ -2921,21 +3060,16 @@ def test_workflows_external_workflow_action_config(pub):
|
|||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (wf.id, st.id))
|
||||
# only action error: custom error message
|
||||
resp.forms[0]['slug'] = 'formdef:external'
|
||||
resp = resp.forms[0].submit('submit')
|
||||
assert 'There were errors processing your form. See below for details.' not in resp
|
||||
assert 'This action is configured in two steps. See below for details.' in resp
|
||||
assert 'required field' in resp
|
||||
# multiple errors: do as usual
|
||||
resp.forms[0]['slug'] = 'formdef:external'
|
||||
resp.forms[0]['condition$type'] = 'django'
|
||||
resp.forms[0]['condition$value_django'] = '{{ 42 }}'
|
||||
assert [
|
||||
(x.attrib.get('value'), x.attrib.get('data-slugs'))
|
||||
for x in resp.pyquery('#form_trigger_id option[data-slugs]')
|
||||
] == [
|
||||
('action:test', 'formdef:external|formdef:other'),
|
||||
('action:test2', 'formdef:external|formdef:other'),
|
||||
]
|
||||
resp = resp.forms[0].submit('submit')
|
||||
assert 'There were errors processing your form. See below for details.' in resp
|
||||
assert 'This action is configured in two steps. See below for details.' not in resp
|
||||
assert 'required field' in resp
|
||||
assert "syntax error: Could not parse the remainder: '{{' from '{{'" in resp
|
||||
|
||||
|
||||
def test_workflows_create_formdata(pub):
|
||||
|
@ -3141,11 +3275,62 @@ def test_workflows_create_formdata_deleted_field(pub):
|
|||
resp = app.get('/backoffice/workflows/%s/status/%s/items/_create_formdata/' % (wf.id, st2.id))
|
||||
assert resp.form['mappings$element1$field_id'].options == [
|
||||
('', False, '---'),
|
||||
('1', False, 'string2'),
|
||||
('1', False, 'string2 - Text (line)'),
|
||||
('0', True, '❗ string1 (deleted field)'),
|
||||
]
|
||||
|
||||
|
||||
def test_workflows_create_formdata_fields_with_same_label(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
BlockDef.wipe()
|
||||
block = BlockDef()
|
||||
block.name = 'Test Block'
|
||||
block.fields = [fields.StringField(id='123', required=True, label='Test')]
|
||||
block.store()
|
||||
|
||||
FormDef.wipe()
|
||||
target_formdef = FormDef()
|
||||
target_formdef.name = 'target form'
|
||||
target_formdef.enable_tracking_codes = True
|
||||
target_formdef.fields = [
|
||||
fields.StringField(id='0', label='string1', varname='foo'),
|
||||
fields.StringField(id='1', label='string1', varname='bar'),
|
||||
fields.BlockField(id='2', label='block1', varname='foo2', block_slug=block.slug),
|
||||
fields.BlockField(id='3', label='block1', varname='bar2', block_slug=block.slug),
|
||||
fields.BlockField(id='4', label='block2', varname='xxx', block_slug=block.slug),
|
||||
]
|
||||
target_formdef.store()
|
||||
|
||||
Workflow.wipe()
|
||||
wf = Workflow(name='create-formdata')
|
||||
|
||||
st2 = wf.add_status('Resubmit')
|
||||
|
||||
create_formdata = st2.add_action('create_formdata', id='_create_formdata')
|
||||
create_formdata.formdef_slug = target_formdef.url_name
|
||||
create_formdata.mappings = [
|
||||
Mapping(field_id='0', expression='{{ "a" }}'),
|
||||
Mapping(field_id='1', expression='{{ "b" }}'),
|
||||
]
|
||||
|
||||
wf.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/workflows/%s/status/%s/items/_create_formdata/' % (wf.id, st2.id))
|
||||
assert resp.form['mappings$element1$field_id'].options == [
|
||||
('', False, '---'),
|
||||
('0', False, 'string1 - Text (line) (foo)'),
|
||||
('1', True, 'string1 - Text (line) (bar)'),
|
||||
('2', False, 'block1 - Field Block (Test Block) (foo2)'),
|
||||
('2$123', False, 'block1 (foo2) - Test - Text (line)'),
|
||||
('3', False, 'block1 - Field Block (Test Block) (bar2)'),
|
||||
('3$123', False, 'block1 (bar2) - Test - Text (line)'),
|
||||
('4', False, 'block2 - Field Block (Test Block)'),
|
||||
('4$123', False, 'block2 - Test - Text (line)'),
|
||||
]
|
||||
|
||||
|
||||
def test_workflows_create_carddata_action_config(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
|
@ -3626,8 +3811,16 @@ def test_workflows_wscall_options(pub, value):
|
|||
baz_status.add_action('webservice_call')
|
||||
workflow.store()
|
||||
|
||||
pub.cfg['debug'] = {}
|
||||
pub.write_cfg()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (workflow.id, baz_status.id))
|
||||
assert 'notify_on_errors' not in resp.form.fields
|
||||
|
||||
pub.cfg['debug'] = {'error_email': 'test@localhost'}
|
||||
pub.write_cfg()
|
||||
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (workflow.id, baz_status.id))
|
||||
assert resp.form['notify_on_errors'].value is None
|
||||
assert resp.form['record_on_errors'].value == 'yes'
|
||||
resp.form['notify_on_errors'] = value
|
||||
|
@ -4212,3 +4405,32 @@ def test_workflows_edit_aggregationemail_action(pub):
|
|||
app = login(get_app(pub))
|
||||
resp = app.get(item.get_admin_url())
|
||||
assert '_submitter' not in [x[0] for x in resp.form['to$element0'].options]
|
||||
|
||||
|
||||
def test_workflows_function_and_role_with_same_name(pub):
|
||||
create_superuser(pub)
|
||||
pub.role_class.wipe()
|
||||
role1 = pub.role_class(name='Foo')
|
||||
role1.store()
|
||||
role2 = pub.role_class(name='Foobar')
|
||||
role2.store()
|
||||
Workflow.wipe()
|
||||
|
||||
workflow = Workflow(name='foo')
|
||||
workflow.roles = {'_receiver': 'Receiver', '_foobar': 'Foobar'}
|
||||
st1 = workflow.add_status(name='baz')
|
||||
commentable = st1.add_action('commentable', id='_commentable')
|
||||
workflow.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get(commentable.get_admin_url())
|
||||
assert resp.form['by$element0'].options == [
|
||||
('None', True, '---'),
|
||||
('_submitter', False, 'User'),
|
||||
('_receiver', False, 'Receiver'),
|
||||
('_foobar', False, 'Foobar'),
|
||||
('logged-users', False, 'Logged Users'),
|
||||
('', False, '----'),
|
||||
(str(role1.id), False, 'Foo'),
|
||||
(str(role2.id), False, 'Foobar [role]'), # same name as function -> role suffix
|
||||
]
|
||||
|
|
|
@ -0,0 +1,862 @@
|
|||
import datetime
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from django.utils.html import escape
|
||||
from django.utils.timezone import make_aware
|
||||
|
||||
from wcs import workflow_tests
|
||||
from wcs.formdef import FormDef, fields
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.testdef import TestDef, WebserviceResponse
|
||||
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef, WorkflowCriticalityLevel
|
||||
|
||||
from ..utilities import create_temporary_pub, get_app, login
|
||||
from .test_all import create_superuser
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pub():
|
||||
pub = create_temporary_pub()
|
||||
|
||||
req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
|
||||
pub.set_app_dir(req)
|
||||
pub.cfg['identification'] = {'methods': ['password']}
|
||||
pub.write_cfg()
|
||||
|
||||
if not pub.site_options.has_section('options'):
|
||||
pub.site_options.add_section('options')
|
||||
pub.site_options.set('options', 'enable-workflow-tests', 'true')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
|
||||
FormDef.wipe()
|
||||
TestDef.wipe()
|
||||
WebserviceResponse.wipe()
|
||||
return pub
|
||||
|
||||
|
||||
def test_workflow_tests_link_feature_flag(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.name = 'First test'
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/' % testdef.id)
|
||||
assert 'Workflow tests' in resp.text
|
||||
|
||||
pub.site_options.set('options', 'enable-workflow-tests', 'false')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/' % testdef.id)
|
||||
assert 'Workflow tests' not in resp.text
|
||||
|
||||
|
||||
def test_workflow_tests_options(pub):
|
||||
create_superuser(pub)
|
||||
user = pub.user_class(name='test user')
|
||||
user.email = 'test@example.com'
|
||||
user.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.name = 'First test'
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/' % testdef.id)
|
||||
resp = resp.click('Options')
|
||||
|
||||
resp.form['agent'] = user.id
|
||||
resp = resp.form.submit('submit').follow()
|
||||
|
||||
testdef = TestDef.get(testdef.id)
|
||||
assert testdef.agent_id == str(user.id)
|
||||
|
||||
|
||||
def test_workflow_tests_disabled_no_agent(pub):
|
||||
user = create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.name = 'First test'
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/' % testdef.id)
|
||||
assert 'Backoffice user is not defined, workflow tests will not be executed.' in resp.text
|
||||
|
||||
resp = resp.click('Open test options')
|
||||
resp.form['agent'] = user.id
|
||||
resp.form.submit().follow()
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/' % testdef.id)
|
||||
assert 'Backoffice user is not defined' not in resp.text
|
||||
assert 'Open test options' not in resp.text
|
||||
|
||||
|
||||
def test_workflow_tests_edit_actions(pub):
|
||||
user = create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.name = 'First test'
|
||||
testdef.agent_id = user.id
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/' % testdef.id)
|
||||
resp = resp.click('Workflow tests')
|
||||
|
||||
assert 'There are no workflow test actions yet.' in resp.text
|
||||
assert len(resp.pyquery('.biglist li')) == 0
|
||||
|
||||
option_labels = [x[2] for x in resp.form['type'].options]
|
||||
assert (
|
||||
option_labels.index('Assert email is sent')
|
||||
< option_labels.index('Assert form status')
|
||||
< option_labels.index('—')
|
||||
< option_labels.index('Move forward in time')
|
||||
< option_labels.index('Simulate click on action button')
|
||||
)
|
||||
|
||||
# add workflow test action through sidebar form
|
||||
resp.form['type'] = 'button-click'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert 'There are no workflow test actions yet.' not in resp.text
|
||||
assert len(resp.pyquery('.biglist li')) == 1
|
||||
assert resp.pyquery('.biglist li .label').text() == 'Simulate click on action button'
|
||||
assert 'not configured' in resp.text
|
||||
|
||||
resp = resp.click('Edit')
|
||||
resp.form['button_name'] = 'Accept'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert 'not configured' not in resp.text
|
||||
assert resp.text.count(escape('Click on "Accept"')) == 1
|
||||
|
||||
resp = resp.click('Duplicate').follow()
|
||||
assert resp.text.count(escape('Click on "Accept"')) == 2
|
||||
|
||||
resp = resp.click('Edit', index=0)
|
||||
resp.form['button_name'] = 'Reject'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert resp.text.count(escape('Click on "Accept"')) == 1
|
||||
assert resp.text.count(escape('Click on "Reject"')) == 1
|
||||
|
||||
resp = resp.click('Delete', index=0)
|
||||
resp = resp.form.submit().follow()
|
||||
assert resp.text.count(escape('Click on "Accept"')) == 1
|
||||
assert resp.text.count(escape('Click on "Reject"')) == 0
|
||||
|
||||
# simulate invalid action
|
||||
testdef = TestDef.get(testdef.id)
|
||||
testdef.workflow_tests.actions[0].key = 'xxx'
|
||||
testdef.store()
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/' % testdef.id)
|
||||
assert 'There are no workflow test actions yet.' in resp.text
|
||||
|
||||
|
||||
def test_workflow_tests_action_button_click(pub):
|
||||
create_superuser(pub)
|
||||
user = pub.user_class(name='test user')
|
||||
user.store()
|
||||
|
||||
workflow = Workflow(name='Workflow One')
|
||||
new_status = workflow.add_status(name='New status')
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.workflow_id = workflow.id
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.ButtonClick(id='1', button_name='Button 4'),
|
||||
]
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/' % testdef.id)
|
||||
assert escape('Click on "Button 4" by backoffice user') in resp.text
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/1/' % testdef.id)
|
||||
assert 'Workflow has no action that displays a button.' in resp.text
|
||||
|
||||
jump = new_status.add_action('choice')
|
||||
jump.label = 'Button 1'
|
||||
jump.status = new_status.id
|
||||
|
||||
jump = new_status.add_action('choice')
|
||||
jump.label = 'Button 2'
|
||||
jump.status = new_status.id
|
||||
|
||||
jump = new_status.add_action('choice')
|
||||
jump.label = 'Button no target status'
|
||||
|
||||
workflow.add_global_action('Action 1')
|
||||
|
||||
interactive_action = workflow.add_global_action('Interactive action (should not be shown)')
|
||||
interactive_action.add_action('form')
|
||||
|
||||
workflow.store()
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/1/' % testdef.id)
|
||||
assert resp.form['button_name'].options == [
|
||||
('Action 1', False, 'Action 1'),
|
||||
('Button 1', False, 'Button 1'),
|
||||
('Button 2', False, 'Button 2'),
|
||||
('Button 4 (not available)', True, 'Button 4 (not available)'),
|
||||
]
|
||||
|
||||
resp.form['button_name'] = 'Button 1'
|
||||
resp.form['who'] = 'submitter'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert escape('Click on "Button 1" by submitter') in resp.text
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/1/' % testdef.id)
|
||||
resp.form['who'] = 'other'
|
||||
resp.form['who_id'].force_value(user.id)
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert escape('Click on "Button 1" by test user') in resp.text
|
||||
|
||||
user.remove_self()
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/' % testdef.id)
|
||||
assert escape('Click on "Button 1" by missing user') in resp.text
|
||||
|
||||
|
||||
def test_workflow_tests_action_assert_status(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.AssertStatus(id='1', status_name='Deleted status'),
|
||||
]
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/1/' % testdef.id)
|
||||
|
||||
assert resp.form['status_name'].options == [
|
||||
('Just Submitted', False, 'Just Submitted'),
|
||||
('New', False, 'New'),
|
||||
('Rejected', False, 'Rejected'),
|
||||
('Accepted', False, 'Accepted'),
|
||||
('Finished', False, 'Finished'),
|
||||
('Deleted status (not available)', False, 'Deleted status (not available)'),
|
||||
]
|
||||
|
||||
|
||||
def test_workflow_tests_action_skip_time(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.SkipTime(id='1'),
|
||||
]
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/1/' % testdef.id)
|
||||
|
||||
resp.form['seconds'] = '1 day 1 hour 1 minute'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert TestDef.get(testdef.id).workflow_tests.actions[0].seconds == 25 * 60 * 60 + 60
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/1/' % testdef.id)
|
||||
assert resp.form['seconds'].value == '1 day, 1 hour and 1 minute'
|
||||
|
||||
resp = resp.form.submit().follow()
|
||||
assert TestDef.get(testdef.id).workflow_tests.actions[0].seconds == 25 * 60 * 60 + 60
|
||||
|
||||
|
||||
def test_workflow_tests_action_assert_email(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.AssertEmail(id='1'),
|
||||
]
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/' % testdef.id)
|
||||
assert 'not configured' not in resp.text
|
||||
assert 'Email to' not in resp.text
|
||||
|
||||
# empty configuration is allowed
|
||||
resp = resp.click('Edit')
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
resp = resp.click('Edit')
|
||||
resp.form['subject_strings$element0'] = 'abc'
|
||||
resp.form['body_strings$element0'] = 'def'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert 'Email to' not in resp.text
|
||||
|
||||
assert_email = TestDef.get(testdef.id).workflow_tests.actions[0]
|
||||
assert assert_email.subject_strings == ['abc']
|
||||
assert assert_email.body_strings == ['def']
|
||||
|
||||
resp = resp.click('Edit')
|
||||
resp.form['addresses$element0'] = 'test@entrouvert.com'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert escape('Email to "test@entrouvert.com"') in resp.text
|
||||
|
||||
assert_email.addresses = ['a@entrouvert.com', 'b@entrouvert.com', 'c@entrouvert.com']
|
||||
assert_email.parent.store()
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/' % testdef.id)
|
||||
assert escape('Email to "a@entrouvert.com" (+2)') in resp.text
|
||||
|
||||
|
||||
def test_workflow_tests_action_assert_sms(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.AssertSMS(id='1'),
|
||||
]
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/' % testdef.id)
|
||||
assert 'not configured' not in resp.text
|
||||
assert 'SMS to' not in resp.text
|
||||
|
||||
# empty configuration is allowed
|
||||
resp = resp.click('Edit')
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
resp = resp.click('Edit')
|
||||
resp.form['phone_numbers$element0'] = '0123456789'
|
||||
resp.form['body'] = 'Hello'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert 'SMS to 0123456789' in resp.text
|
||||
|
||||
assert_sms = TestDef.get(testdef.id).workflow_tests.actions[0]
|
||||
assert assert_sms.phone_numbers == ['0123456789']
|
||||
assert assert_sms.body == 'Hello'
|
||||
|
||||
assert_sms.phone_numbers = ['0123456789', '0123456781', '0123456782']
|
||||
assert_sms.parent.store()
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/' % testdef.id)
|
||||
assert escape('SMS to 0123456789 (+2)') in resp.text
|
||||
|
||||
|
||||
def test_workflow_tests_action_assert_anonymise(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.AssertAnonymise(id='1'),
|
||||
]
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/' % testdef.id)
|
||||
assert 'Edit' not in resp.text
|
||||
|
||||
|
||||
def test_workflow_tests_action_assert_redirect(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.AssertRedirect(id='1'),
|
||||
]
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/' % testdef.id)
|
||||
assert 'not configured' in resp.text
|
||||
|
||||
resp = resp.click('Edit')
|
||||
resp.form['url'] = 'http://example.com'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert 'not configured' not in resp.text
|
||||
assert 'http://example.com' in resp.text
|
||||
|
||||
|
||||
def test_workflow_tests_action_assert_history_message(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.AssertHistoryMessage(id='1'),
|
||||
]
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/' % testdef.id)
|
||||
assert 'not configured' in resp.text
|
||||
|
||||
resp = resp.click('Edit')
|
||||
resp.form['message'] = 'Hello'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert 'not configured' not in resp.text
|
||||
|
||||
|
||||
def test_workflow_tests_action_assert_alert(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.AssertAlert(id='1'),
|
||||
]
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/' % testdef.id)
|
||||
assert 'not configured' in resp.text
|
||||
|
||||
resp = resp.click('Edit')
|
||||
resp.form['message'] = 'Hello'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert 'not configured' not in resp.text
|
||||
|
||||
|
||||
def test_workflow_tests_action_assert_criticality(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
workflow = Workflow(name='Workflow One')
|
||||
workflow.add_status(name='New status')
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.workflow_id = workflow.id
|
||||
formdef.name = 'test title'
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.AssertCriticality(id='1'),
|
||||
]
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/' % testdef.id)
|
||||
assert 'not configured' in resp.text
|
||||
|
||||
resp = resp.click('Edit')
|
||||
assert 'Workflow has no criticality levels.' in resp.text
|
||||
|
||||
workflow.criticality_levels = [
|
||||
WorkflowCriticalityLevel(name='green'),
|
||||
WorkflowCriticalityLevel(name='red'),
|
||||
]
|
||||
workflow.store()
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/1/' % testdef.id)
|
||||
resp.form['level_id'].select(text='green')
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert 'not configured' not in resp.text
|
||||
assert escape('Criticality is "green"') in resp.text
|
||||
|
||||
|
||||
def test_workflow_tests_action_assert_backoffice_field(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
workflow = Workflow(name='Workflow One')
|
||||
workflow.add_status(name='New status')
|
||||
|
||||
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
|
||||
workflow.backoffice_fields_formdef.fields = [
|
||||
fields.StringField(id='bo1', label='Text'),
|
||||
fields.StringField(id='bo2', label='Text 2'),
|
||||
]
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.workflow = workflow
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.AssertBackofficeFieldValues(id='1'),
|
||||
]
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/1/' % testdef.id)
|
||||
assert resp.form['fields$element0$field_id'].options == [
|
||||
('', False, ''),
|
||||
('bo1', False, 'Text - Text (line)'),
|
||||
('bo2', False, 'Text 2 - Text (line)'),
|
||||
]
|
||||
|
||||
resp.form['fields$element0$field_id'] = 'bo2'
|
||||
resp.form['fields$element0$value'] = 'xxx'
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert_bakoffice_field_values = TestDef.get(testdef.id).workflow_tests.actions[0]
|
||||
assert assert_bakoffice_field_values.fields == [
|
||||
{'field_id': 'bo2', 'value': 'xxx'},
|
||||
]
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/1/' % testdef.id)
|
||||
assert resp.form['fields$element0$field_id'].value == 'bo2'
|
||||
assert resp.form['fields$element0$value'].value == 'xxx'
|
||||
|
||||
|
||||
def test_workflow_tests_action_assert_webservice_call(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.AssertWebserviceCall(id='1'),
|
||||
]
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/1/' % testdef.id)
|
||||
assert 'you must define corresponding webservice response' in resp.text
|
||||
|
||||
resp = resp.click('Add webservice response')
|
||||
assert 'There are no webservice responses yet.' in resp.text
|
||||
|
||||
response = WebserviceResponse()
|
||||
response.testdef_id = testdef.id
|
||||
response.name = 'Fake response'
|
||||
response.store()
|
||||
|
||||
response2 = WebserviceResponse()
|
||||
response2.testdef_id = testdef.id
|
||||
response2.name = 'Fake response 2'
|
||||
response2.store()
|
||||
|
||||
response3 = WebserviceResponse()
|
||||
response3.testdef_id = testdef.id + 1
|
||||
response3.name = 'Other response'
|
||||
response3.store()
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/1/' % testdef.id)
|
||||
assert resp.form['webservice_response_id'].options == [
|
||||
(str(response.id), False, 'Fake response'),
|
||||
(str(response2.id), False, 'Fake response 2'),
|
||||
]
|
||||
assert resp.form['call_count'].value == '1'
|
||||
|
||||
resp.form['webservice_response_id'] = 1
|
||||
resp.form['call_count'] = 2
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
assert 'Fake response' in resp.text
|
||||
assert 'Broken' not in resp.text
|
||||
|
||||
assert_webservice_call = TestDef.get(testdef.id).workflow_tests.actions[0]
|
||||
assert assert_webservice_call.webservice_response_id == '1'
|
||||
assert assert_webservice_call.call_count == 2
|
||||
|
||||
response.remove_self()
|
||||
resp = app.get('/backoffice/forms/1/tests/%s/workflow/' % testdef.id)
|
||||
|
||||
assert 'Broken, missing webservice response' in resp.text
|
||||
assert 'Fake response' not in resp.text
|
||||
|
||||
|
||||
def test_workflow_tests_actions_reorder(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.name = 'First test'
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.ButtonClick(id='0', button_name='First'),
|
||||
workflow_tests.ButtonClick(id='1', button_name='Second'),
|
||||
workflow_tests.ButtonClick(id='2', button_name='Third'),
|
||||
workflow_tests.ButtonClick(id='3', button_name='Fourth'),
|
||||
]
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
url = '/backoffice/forms/%s/tests/%s/workflow/update_order' % (formdef.id, testdef.id)
|
||||
|
||||
# missing element in params: do nothing
|
||||
resp = app.get(url + '?order=0;3;1;2;')
|
||||
assert resp.json == {'success': 'ko'}
|
||||
|
||||
# missing order in params: do nothing
|
||||
resp = app.get(url + '?element=0')
|
||||
assert resp.json == {'success': 'ko'}
|
||||
|
||||
resp = app.get(url + '?order=0;3;1;2;&element=3')
|
||||
assert resp.json == {'success': 'ok'}
|
||||
testdef = TestDef.get(testdef.id)
|
||||
assert [x.id for x in testdef.workflow_tests.actions] == ['0', '3', '1', '2']
|
||||
|
||||
# unknown id: ignored
|
||||
resp = app.get(url + '?order=0;1;2;3;4;&element=3')
|
||||
assert resp.json == {'success': 'ok'}
|
||||
testdef = TestDef.get(testdef.id)
|
||||
assert [x.id for x in testdef.workflow_tests.actions] == ['0', '1', '2', '3']
|
||||
|
||||
# missing id: do nothing
|
||||
resp = app.get(url + '?order=0;3;1;&element=3')
|
||||
assert resp.json == {'success': 'ko'}
|
||||
testdef = TestDef.get(testdef.id)
|
||||
assert [x.id for x in testdef.workflow_tests.actions] == ['0', '1', '2', '3']
|
||||
|
||||
|
||||
def test_workflow_tests_run(pub):
|
||||
user = create_superuser(pub)
|
||||
|
||||
role = pub.role_class(name='test role')
|
||||
role.store()
|
||||
user.roles = [role.id]
|
||||
user.store()
|
||||
|
||||
workflow = Workflow(name='Workflow One')
|
||||
new_status = workflow.add_status(name='New status')
|
||||
|
||||
sendmail = new_status.add_action('sendmail')
|
||||
sendmail.to = ['test@example.org']
|
||||
sendmail.subject = 'Hello'
|
||||
sendmail.body = 'abc'
|
||||
|
||||
jump = new_status.add_action('choice')
|
||||
jump.label = 'Loop on status'
|
||||
jump.status = new_status.id
|
||||
jump.by = [role.id]
|
||||
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.workflow_id = workflow.id
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.agent_id = user.id
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.ButtonClick(id='1', button_name='Loop on status'),
|
||||
]
|
||||
testdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/results/')
|
||||
resp = resp.click('Run tests').follow()
|
||||
|
||||
assert len(resp.pyquery('tr')) == 1
|
||||
assert 'Success!' in resp.text
|
||||
|
||||
# change button label
|
||||
jump.label = 'xxx'
|
||||
workflow.store()
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/results/')
|
||||
resp = resp.click('Run tests').follow()
|
||||
|
||||
assert escape('Workflow error: Button "Loop on status" is not displayed.') in resp.text
|
||||
|
||||
resp = resp.click('Display details')
|
||||
|
||||
assert 'Form status when error occured: New status' in resp.text
|
||||
assert resp.pyquery('li#test-action').text() == 'Test action: Simulate click on action button'
|
||||
assert (
|
||||
resp.pyquery('li#test-action a').attr('href')
|
||||
== 'http://example.net/backoffice/forms/1/tests/%s/workflow/#1' % testdef.id
|
||||
)
|
||||
|
||||
testdef.workflow_tests.actions = []
|
||||
testdef.store()
|
||||
|
||||
resp = app.get(resp.request.url)
|
||||
assert 'Form status when error occured: New status' in resp.text
|
||||
assert resp.pyquery('li#test-action').text() == 'Test action: deleted'
|
||||
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.AssertEmail(id='1', body_strings=['def']),
|
||||
]
|
||||
testdef.store()
|
||||
|
||||
resp = app.get('/backoffice/forms/1/tests/results/')
|
||||
resp = resp.click('Run tests').follow()
|
||||
assert escape('Email body does not contain "def".') in resp.text
|
||||
|
||||
resp = resp.click('Display details')
|
||||
assert 'Form status when error occured: New status' in resp.text
|
||||
assert 'Email body: \nabc' in resp.text
|
||||
assert resp.pyquery('li#test-action').text() == 'Test action: Assert email is sent'
|
||||
|
||||
|
||||
def test_workfow_tests_creation_from_formdata(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
workflow = Workflow(name='Workflow One')
|
||||
new_status = workflow.add_status(name='New status')
|
||||
end_status = workflow.add_status(name='End status')
|
||||
|
||||
jump = new_status.add_action('jump')
|
||||
jump.status = end_status.id
|
||||
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.workflow_id = workflow.id
|
||||
formdef.name = 'test title'
|
||||
formdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2022, 1, 1, 0, 0))
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
formdata.store()
|
||||
|
||||
resp = app.get('/backoffice/forms/%s/tests/new' % formdef.id)
|
||||
resp.form['name'] = 'First test'
|
||||
resp.form['creation_mode'] = 'formdata-wf'
|
||||
resp.form['formdata'].select(text='1-1 - Unknown User - 2022-01-01 00:00')
|
||||
resp = resp.form.submit().follow()
|
||||
|
||||
testdef = TestDef.select()[0]
|
||||
assert len(testdef.workflow_tests.actions) == 1
|
||||
assert testdef.workflow_tests.actions[0].key == 'assert-status'
|
||||
assert testdef.workflow_tests.actions[0].status_name == 'End status'
|
|
@ -1,4 +1,6 @@
|
|||
import io
|
||||
import os
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import pytest
|
||||
|
@ -207,6 +209,23 @@ def test_wscalls_import(pub, wscall):
|
|||
resp = resp.form.submit()
|
||||
assert 'Invalid File' in resp.text
|
||||
|
||||
# python expression
|
||||
if not pub.site_options.has_section('options'):
|
||||
pub.site_options.add_section('options')
|
||||
pub.site_options.set('options', 'forbid-new-python-expressions', 'true')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
wscall.request = {'url': 'http://example.net', 'qs_data': {'a': '=1+2'}}
|
||||
wscall.store()
|
||||
resp = app.get('/backoffice/settings/wscalls/%s/' % wscall.id)
|
||||
resp = resp.click(href=re.compile('^export$'))
|
||||
xml_export = resp.text
|
||||
resp = app.get('/backoffice/settings/wscalls/')
|
||||
resp = resp.click(href='import')
|
||||
resp.form['file'] = Upload('wscall', xml_export.encode('utf-8'))
|
||||
resp = resp.form.submit()
|
||||
assert 'Python expression detected' in resp
|
||||
|
||||
|
||||
def test_wscalls_empty_param_values(pub):
|
||||
create_superuser(pub)
|
||||
|
|
|
@ -125,57 +125,24 @@ def test_tracking_code(pub, auth, admin_user):
|
|||
resp = get_url('/api/code/%s' % code.id, status=404)
|
||||
|
||||
|
||||
def test_validate_expression(pub):
|
||||
resp = get_app(pub).get('/api/validate-expression?expression=hello')
|
||||
assert resp.json == {'klass': None, 'msg': ''}
|
||||
resp = get_app(pub).get('/api/validate-expression?expression=[hello]')
|
||||
assert resp.json == {'klass': None, 'msg': ''}
|
||||
resp = get_app(pub).get('/api/validate-expression?expression==[hello')
|
||||
assert resp.json['klass'] == 'error'
|
||||
assert resp.json['msg'].startswith('syntax error')
|
||||
resp = get_app(pub).get('/api/validate-expression?expression==[hello]')
|
||||
assert resp.json['klass'] == 'warning'
|
||||
assert resp.json['msg'].startswith('Make sure you want a Python expression,')
|
||||
resp = get_app(pub).get('/api/validate-expression?expression==hello[0]')
|
||||
assert resp.json == {'klass': None, 'msg': ''}
|
||||
resp = get_app(pub).get('/api/validate-expression?expression==hello[\'plop\']')
|
||||
assert resp.json == {'klass': None, 'msg': ''}
|
||||
# django with unicode
|
||||
resp = get_app(pub).get('/api/validate-expression?expression={{hello+%C3%A9l%C3%A9phant}}')
|
||||
assert resp.json['klass'] == 'error'
|
||||
assert resp.json['msg'].startswith('syntax error in Django template: Could not parse the remainder')
|
||||
# broken ezt
|
||||
resp = get_app(pub).get('/api/validate-expression?expression=[for]')
|
||||
assert resp.json == {
|
||||
'klass': 'error',
|
||||
'msg': 'syntax error in ezt template: wrong number of arguments at line 1 and column 1',
|
||||
}
|
||||
|
||||
|
||||
def test_validate_condition(pub):
|
||||
resp = get_app(pub).get('/api/validate-condition?type=python&value_python=hello')
|
||||
assert resp.json == {'klass': None, 'msg': ''}
|
||||
assert resp.json == {'msg': ''}
|
||||
resp = get_app(pub).get('/api/validate-condition?type=python&value_python=~2')
|
||||
assert resp.json == {'klass': None, 'msg': ''}
|
||||
assert resp.json == {'msg': ''}
|
||||
resp = get_app(pub).get('/api/validate-condition?type=python&value_python=hello -')
|
||||
assert resp.json['klass'] == 'error'
|
||||
assert resp.json['msg'].startswith('syntax error')
|
||||
resp = get_app(pub).get('/api/validate-condition?type=python&value_python={{form_number}}==3')
|
||||
assert resp.json['klass'] == 'error'
|
||||
assert 'Python condition cannot contain {{' in resp.json['msg']
|
||||
|
||||
resp = get_app(pub).get('/api/validate-condition?type=django&value_django=un+%C3%A9l%C3%A9phant')
|
||||
assert resp.json['klass'] == 'error'
|
||||
assert resp.json['msg'].startswith("syntax error: Unused 'éléphant'")
|
||||
resp = get_app(pub).get('/api/validate-condition?type=django&value_django=~2')
|
||||
assert resp.json['klass'] == 'error'
|
||||
assert resp.json['msg'].startswith('syntax error')
|
||||
resp = get_app(pub).get('/api/validate-condition?type=django&value_django=%22...%22+inf') # "..." + inf
|
||||
assert resp.json['klass'] == 'error'
|
||||
assert resp.json['msg'].startswith('syntax error')
|
||||
|
||||
resp = get_app(pub).get('/api/validate-condition?type=unknown&value_unknown=2')
|
||||
assert resp.json['klass'] == 'error'
|
||||
assert resp.json['msg'] == 'unknown condition type'
|
||||
|
||||
|
||||
|
@ -210,6 +177,16 @@ def test_reverse_geocoding(pub):
|
|||
== 'https://nominatim.entrouvert.org/reverse?zoom=16&key=KEY&format=json&addressdetails=1&lat=0&lon=0&accept-language=en'
|
||||
)
|
||||
|
||||
pub.site_options.set('options', 'nominatim_contact_email', 'test@example.net')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
resp = get_app(pub).get('/api/reverse-geocoding?lat=0&lon=0')
|
||||
assert (
|
||||
rsps.calls[-1].request.url
|
||||
== 'https://nominatim.entrouvert.org/reverse?zoom=16&key=KEY&email=test%40example.net&'
|
||||
'format=json&addressdetails=1&lat=0&lon=0&accept-language=en'
|
||||
)
|
||||
|
||||
pub.site_options.set(
|
||||
'options', 'reverse_geocoding_service_url', 'http://reverse.example.net/?param=value'
|
||||
)
|
||||
|
@ -242,7 +219,7 @@ def test_geocoding(pub):
|
|||
pub.site_options.write(fd)
|
||||
resp = get_app(pub).get('/api/geocoding?q=test')
|
||||
assert rsps.calls[-1].request.url == (
|
||||
'https://nominatim.entrouvert.org/search?viewbox=2.34,1.23,3.45,2.34&bounded=1&'
|
||||
'https://nominatim.entrouvert.org/search?viewbox=2.34%2C1.23%2C3.45%2C2.34&bounded=1&'
|
||||
'format=json&q=test&accept-language=en'
|
||||
)
|
||||
|
||||
|
@ -263,7 +240,7 @@ def test_geocoding(pub):
|
|||
pub.site_options.write(fd)
|
||||
resp = get_app(pub).get('/api/geocoding?q=test')
|
||||
assert rsps.calls[-1].request.url == (
|
||||
'https://nominatim.entrouvert.org/search?key=KEY&viewbox=2.34,1.23,3.45,2.34&bounded=1&'
|
||||
'https://nominatim.entrouvert.org/search?key=KEY&viewbox=2.34%2C1.23%2C3.45%2C2.34&bounded=1&'
|
||||
'format=json&q=test&accept-language=en'
|
||||
)
|
||||
|
||||
|
|
|
@ -193,6 +193,17 @@ def test_carddata_include_params(pub, local_user, auth):
|
|||
resp = get_url('/api/cards/test/%s/?include-workflow=off&include-workflow-data=off' % carddata.id)
|
||||
assert 'workflow' not in resp.json
|
||||
|
||||
resp = get_url('/api/cards/test/list')
|
||||
assert len(resp.json['data']) == 1
|
||||
|
||||
carddata.anonymise()
|
||||
resp = get_url('/api/cards/test/list')
|
||||
assert len(resp.json['data']) == 0
|
||||
|
||||
carddata.anonymise()
|
||||
resp = get_url('/api/cards/test/list?include-anonymised=on')
|
||||
assert len(resp.json['data']) == 1
|
||||
|
||||
|
||||
def test_carddata_user_fields(pub, local_user):
|
||||
pub.role_class.wipe()
|
||||
|
@ -356,7 +367,7 @@ def test_api_list_formdata_phone_order_by_rank(pub):
|
|||
CardDef.wipe()
|
||||
carddef = CardDef()
|
||||
carddef.name = 'test'
|
||||
carddef.workflow_roles = {'_receiver': role.id}
|
||||
carddef.workflow_roles = {'_viewer': role.id}
|
||||
carddef.fields = [
|
||||
fields.StringField(id='0', label='a', display_locations=['listings']),
|
||||
fields.StringField(id='1', label='b'),
|
||||
|
@ -455,7 +466,7 @@ def test_api_card_list_custom_id_filter_identifier(pub):
|
|||
CardDef.wipe()
|
||||
carddef = CardDef()
|
||||
carddef.name = 'foo'
|
||||
carddef.workflow_roles = {'_receiver': role.id}
|
||||
carddef.workflow_roles = {'_viewer': role.id}
|
||||
carddef.fields = [
|
||||
fields.StringField(id='1', label='Test', varname='foo'),
|
||||
]
|
||||
|
@ -473,9 +484,18 @@ def test_api_card_list_custom_id_filter_identifier(pub):
|
|||
'/api/cards/foo/list?filter-identifier=bar', orig=access.access_identifier, key=access.access_key
|
||||
)
|
||||
)
|
||||
assert len(resp.json) == 1
|
||||
assert len(resp.json['data']) == 1
|
||||
assert resp.json['data'][0]['id'] == 'bar'
|
||||
assert resp.json['data'][0]['internal_id'] == str(card.id)
|
||||
|
||||
app = get_app(pub)
|
||||
app.set_authorization(('Basic', ('test', '12345')))
|
||||
resp = app.get('/api/cards/foo/list?filter-identifier=bar')
|
||||
assert len(resp.json) == 1
|
||||
assert len(resp.json['data']) == 1
|
||||
assert resp.json['data'][0]['id'] == 'bar'
|
||||
assert resp.json['data'][0]['internal_id'] == str(card.id)
|
||||
|
||||
resp = app.get('/api/cards/foo/list?filter-identifier=bar&full=on')
|
||||
assert len(resp.json['data']) == 1
|
||||
assert resp.json['data'][0]['id'] == 'bar'
|
||||
assert resp.json['data'][0]['internal_id'] == str(card.id)
|
||||
|
|
|
@ -497,7 +497,7 @@ def test_api_list_status_filter_different(pub, local_user):
|
|||
|
||||
resp = get_app(pub).get(sign_uri('/api/cards/test/list/custom-view/'))
|
||||
assert len(resp.json['data']) == 1
|
||||
assert resp.json['data'][0]['id'] == carddata2.id
|
||||
assert resp.json['data'][0]['id'] == str(carddata2.id)
|
||||
|
||||
resp = get_app(pub).get(sign_uri('/api/cards/test/custom-view/%s/' % carddata2.id))
|
||||
assert resp.json['id'] == str(carddata2.id)
|
||||
|
|
|
@ -7,6 +7,7 @@ import xml.etree.ElementTree as ET
|
|||
|
||||
import pytest
|
||||
|
||||
from wcs.api_export_import import BundleDeclareJob, BundleImportJob, klass_to_slug
|
||||
from wcs.applications import Application, ApplicationElement
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.carddef import CardDef
|
||||
|
@ -151,6 +152,8 @@ def test_export_import_dependencies(pub):
|
|||
wscall.store()
|
||||
wscall = NamedWsCall(name='Test in lateral template')
|
||||
wscall.store()
|
||||
wscall = NamedWsCall(name='Test in loop items template')
|
||||
wscall.store()
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'Test'
|
||||
|
@ -164,6 +167,9 @@ def test_export_import_dependencies(pub):
|
|||
carddef = CardDef()
|
||||
carddef.name = 'Test quater'
|
||||
carddef.store()
|
||||
carddef = CardDef()
|
||||
carddef.name = 'Test in loop items template'
|
||||
carddef.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'Test bis'
|
||||
|
@ -211,8 +217,11 @@ def test_export_import_dependencies(pub):
|
|||
workflow.variables_formdef.fields = [StringField(label='Test', id='1')]
|
||||
|
||||
status = workflow.add_status('New')
|
||||
status.loop_items_template = '{{ webservice.test_in_loop_items_template }}'
|
||||
action = status.add_action('form')
|
||||
action.by = [role.id]
|
||||
status = workflow.add_status('Next')
|
||||
status.loop_items_template = '{{ cards|objects:"test-in-loop-items-template" }}'
|
||||
|
||||
data_source = NamedDataSource(name='foobar')
|
||||
data_source.store()
|
||||
|
@ -367,10 +376,12 @@ def test_export_import_dependencies(pub):
|
|||
('test_bis', 'wscalls'),
|
||||
('test_ter', 'wscalls'),
|
||||
('test_sexies', 'wscalls'),
|
||||
('test_in_loop_items_template', 'wscalls'),
|
||||
('test', 'cards'),
|
||||
('test-bis', 'cards'),
|
||||
('test-ter', 'cards'),
|
||||
('test-quater', 'cards'),
|
||||
('test-in-loop-items-template', 'cards'),
|
||||
('test-ter', 'forms'),
|
||||
('test-bis', 'forms'),
|
||||
('test-quinquies', 'forms'),
|
||||
|
@ -401,8 +412,10 @@ def test_export_import_dependencies(pub):
|
|||
('foobaz', 'data-sources'),
|
||||
('test_bis', 'wscalls'),
|
||||
('test_ter', 'wscalls'),
|
||||
('test_in_loop_items_template', 'wscalls'),
|
||||
('test-bis', 'cards'),
|
||||
('test-ter', 'cards'),
|
||||
('test-in-loop-items-template', 'cards'),
|
||||
('test-bis', 'forms'),
|
||||
('test', 'blocks'),
|
||||
('test-mail-template', 'mail-templates'),
|
||||
|
@ -413,7 +426,7 @@ def test_export_import_dependencies(pub):
|
|||
if dependency['type'] == 'roles':
|
||||
continue
|
||||
get_app(pub).get(sign_uri(dependency['urls']['export']))
|
||||
resp = get_app(pub).get(sign_uri(resp.json['data'][-2]['urls']['dependencies']))
|
||||
resp = get_app(pub).get(sign_uri(resp.json['data'][-3]['urls']['dependencies']))
|
||||
assert resp.json['data'] == []
|
||||
|
||||
cat = MailTemplateCategory(name='Cat')
|
||||
|
@ -927,6 +940,239 @@ def test_export_import_bundle_import(pub):
|
|||
assert formdef.disabled is True
|
||||
assert formdef.workflow_roles == {'_receiver': extra_role.id}
|
||||
|
||||
# bad file format
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-import/'), b'garbage')
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'failed'
|
||||
job = BundleImportJob.get(afterjob_url.split('/')[-2])
|
||||
assert job.status == 'failed'
|
||||
assert job.failure_label == 'Error: Invalid tar file.'
|
||||
|
||||
# missing manifest
|
||||
tar_io = io.BytesIO()
|
||||
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
||||
foo_fd = io.BytesIO(json.dumps({'foo': 'bar'}, indent=2).encode())
|
||||
tarinfo = tarfile.TarInfo('foo.json')
|
||||
tarinfo.size = len(foo_fd.getvalue())
|
||||
tar.addfile(tarinfo, fileobj=foo_fd)
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-import/'), tar_io.getvalue())
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'failed'
|
||||
job = BundleImportJob.get(afterjob_url.split('/')[-2])
|
||||
assert job.status == 'failed'
|
||||
assert job.failure_label == 'Error: Invalid tar file, missing manifest.'
|
||||
|
||||
# missing component
|
||||
tar_io = io.BytesIO()
|
||||
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
||||
manifest_json = {
|
||||
'application': 'Test',
|
||||
'slug': 'test',
|
||||
'elements': [{'type': 'forms', 'slug': 'foo', 'name': 'foo'}],
|
||||
}
|
||||
manifest_fd = io.BytesIO(json.dumps(manifest_json, indent=2).encode())
|
||||
tarinfo = tarfile.TarInfo('manifest.json')
|
||||
tarinfo.size = len(manifest_fd.getvalue())
|
||||
tar.addfile(tarinfo, fileobj=manifest_fd)
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-import/'), tar_io.getvalue())
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'failed'
|
||||
job = BundleImportJob.get(afterjob_url.split('/')[-2])
|
||||
assert job.status == 'failed'
|
||||
assert job.failure_label == 'Error: Invalid tar file, missing component forms/foo.'
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'category_class',
|
||||
[
|
||||
Category,
|
||||
CardDefCategory,
|
||||
BlockCategory,
|
||||
WorkflowCategory,
|
||||
MailTemplateCategory,
|
||||
CommentTemplateCategory,
|
||||
DataSourceCategory,
|
||||
],
|
||||
)
|
||||
def test_export_import_bundle_import_categories_ordering(pub, category_class):
|
||||
category_class.wipe()
|
||||
category = category_class(name='cat 1')
|
||||
category.position = 1
|
||||
category.store()
|
||||
category = category_class(name='cat 2')
|
||||
category.position = 2
|
||||
category.store()
|
||||
category = category_class(name='cat 3')
|
||||
category.position = 3
|
||||
category.store()
|
||||
bundle = create_bundle(
|
||||
[
|
||||
{'type': klass_to_slug[category_class], 'slug': 'cat-1', 'name': 'cat 1'},
|
||||
{'type': klass_to_slug[category_class], 'slug': 'cat-2', 'name': 'cat 2'},
|
||||
{'type': klass_to_slug[category_class], 'slug': 'cat-3', 'name': 'cat 3'},
|
||||
],
|
||||
('%s/cat-1' % klass_to_slug[category_class], category_class.get(1)),
|
||||
('%s/cat-2' % klass_to_slug[category_class], category_class.get(2)),
|
||||
('%s/cat-3' % klass_to_slug[category_class], category_class.get(3)),
|
||||
)
|
||||
|
||||
# delete categories
|
||||
category_class.wipe()
|
||||
# and recreate only cat 4 and 5 in first positions
|
||||
category = category_class(name='cat 4')
|
||||
category.position = 1
|
||||
category.store()
|
||||
category = category_class(name='cat 5')
|
||||
category.position = 2
|
||||
category.store()
|
||||
|
||||
# import bundle
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-import/'), bundle)
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'completed'
|
||||
|
||||
# cat 1, 2, 3 are placed at the end
|
||||
assert category_class.get_by_slug('cat-4').position == 1
|
||||
assert category_class.get_by_slug('cat-5').position == 2
|
||||
assert category_class.get_by_slug('cat-1').position == 3
|
||||
assert category_class.get_by_slug('cat-2').position == 4
|
||||
assert category_class.get_by_slug('cat-3').position == 5
|
||||
|
||||
# delete categories
|
||||
category_class.wipe()
|
||||
# recreate only cat 2, cat 4, cat 5 in this order
|
||||
category = category_class(name='cat 2')
|
||||
category.position = 1
|
||||
category.store()
|
||||
category = category_class(name='cat 4')
|
||||
category.position = 2
|
||||
category.store()
|
||||
category = category_class(name='cat 5')
|
||||
category.position = 3
|
||||
category.store()
|
||||
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-import/'), bundle)
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'completed'
|
||||
|
||||
# cat 1, 2, 3 are placed after cat 4
|
||||
assert category_class.get_by_slug('cat-1').position == 1
|
||||
assert category_class.get_by_slug('cat-2').position == 2
|
||||
assert category_class.get_by_slug('cat-3').position == 3
|
||||
assert category_class.get_by_slug('cat-4').position == 4
|
||||
assert category_class.get_by_slug('cat-5').position == 5
|
||||
|
||||
# delete categories
|
||||
category_class.wipe()
|
||||
# recreate only cat 4, cat 2, cat 5 in this order
|
||||
category = category_class(name='cat 4')
|
||||
category.position = 1
|
||||
category.store()
|
||||
category = category_class(name='cat 2')
|
||||
category.position = 2
|
||||
category.store()
|
||||
category = category_class(name='cat 5')
|
||||
category.position = 3
|
||||
category.store()
|
||||
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-import/'), bundle)
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'completed'
|
||||
|
||||
# cat 1, 2, 3 are placed after cat 4
|
||||
assert category_class.get_by_slug('cat-4').position == 1
|
||||
assert category_class.get_by_slug('cat-1').position == 2
|
||||
assert category_class.get_by_slug('cat-2').position == 3
|
||||
assert category_class.get_by_slug('cat-3').position == 4
|
||||
assert category_class.get_by_slug('cat-5').position == 5
|
||||
|
||||
# delete categories
|
||||
category_class.wipe()
|
||||
# recreate only cat 4, cat 5, cat 2 in this order
|
||||
category = category_class(name='cat 4')
|
||||
category.position = 1
|
||||
category.store()
|
||||
category = category_class(name='cat 5')
|
||||
category.position = 2
|
||||
category.store()
|
||||
category = category_class(name='cat 2')
|
||||
category.position = 3
|
||||
category.store()
|
||||
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-import/'), bundle)
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'completed'
|
||||
|
||||
# cat 1, 2, 3 are placed after cat 4
|
||||
assert category_class.get_by_slug('cat-4').position == 1
|
||||
assert category_class.get_by_slug('cat-5').position == 2
|
||||
assert category_class.get_by_slug('cat-1').position == 3
|
||||
assert category_class.get_by_slug('cat-2').position == 4
|
||||
assert category_class.get_by_slug('cat-3').position == 5
|
||||
|
||||
# delete categories
|
||||
category_class.wipe()
|
||||
# recreate only cat 4, cat 2, cat1 cat 5 in this order but with weird positions
|
||||
category = category_class(name='cat 4')
|
||||
category.position = 4
|
||||
category.store()
|
||||
category = category_class(name='cat 2')
|
||||
category.position = 12
|
||||
category.store()
|
||||
category = category_class(name='cat 1')
|
||||
category.position = 13
|
||||
category.store()
|
||||
category = category_class(name='cat 5')
|
||||
category.position = 20
|
||||
category.store()
|
||||
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-import/'), bundle)
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'completed'
|
||||
|
||||
# cat 1, 2, 3 are placed after cat 4
|
||||
assert category_class.get_by_slug('cat-4').position == 1
|
||||
assert category_class.get_by_slug('cat-1').position == 2
|
||||
assert category_class.get_by_slug('cat-2').position == 3
|
||||
assert category_class.get_by_slug('cat-3').position == 4
|
||||
assert category_class.get_by_slug('cat-5').position == 5
|
||||
|
||||
# delete categories
|
||||
category_class.wipe()
|
||||
# recreate only cat 4, cat 2, cat1 cat 5 in this order but with weird positions
|
||||
category = category_class(name='cat 4')
|
||||
category.position = 1
|
||||
category.store()
|
||||
category = category_class(name='cat 2')
|
||||
category.position = 2
|
||||
category.store()
|
||||
category = category_class(name='cat 1')
|
||||
category.position = 2
|
||||
category.store()
|
||||
category = category_class(name='cat 5')
|
||||
category.position = None # no position
|
||||
category.store()
|
||||
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-import/'), bundle)
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'completed'
|
||||
|
||||
# cat 1, 2, 3 are placed after cat 4
|
||||
assert category_class.get_by_slug('cat-4').position == 1
|
||||
assert category_class.get_by_slug('cat-1').position == 2
|
||||
assert category_class.get_by_slug('cat-2').position == 3
|
||||
assert category_class.get_by_slug('cat-3').position == 4
|
||||
assert category_class.get_by_slug('cat-5').position == 5
|
||||
|
||||
|
||||
def test_export_import_formdef_do_not_overwrite_table_name(pub):
|
||||
formdef = FormDef()
|
||||
|
@ -1119,6 +1365,30 @@ def test_export_import_bundle_declare(pub):
|
|||
== []
|
||||
)
|
||||
|
||||
# bad file format
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-declare/'), b'garbage')
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'failed'
|
||||
job = BundleDeclareJob.get(afterjob_url.split('/')[-2])
|
||||
assert job.status == 'failed'
|
||||
assert job.failure_label == 'Error: Invalid tar file.'
|
||||
|
||||
# missing manifest
|
||||
tar_io = io.BytesIO()
|
||||
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
||||
foo_fd = io.BytesIO(json.dumps({'foo': 'bar'}, indent=2).encode())
|
||||
tarinfo = tarfile.TarInfo('foo.json')
|
||||
tarinfo.size = len(foo_fd.getvalue())
|
||||
tar.addfile(tarinfo, fileobj=foo_fd)
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-declare/'), tar_io.getvalue())
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'failed'
|
||||
job = BundleDeclareJob.get(afterjob_url.split('/')[-2])
|
||||
assert job.status == 'failed'
|
||||
assert job.failure_label == 'Error: Invalid tar file, missing manifest.'
|
||||
|
||||
|
||||
def test_export_import_bundle_unlink(pub):
|
||||
application = Application()
|
||||
|
@ -1694,6 +1964,38 @@ def test_export_import_bundle_check(pub):
|
|||
},
|
||||
}
|
||||
|
||||
# bad file format
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-check/'), b'garbage')
|
||||
assert resp.json['err_desc'] == 'Invalid tar file'
|
||||
|
||||
# missing manifest
|
||||
tar_io = io.BytesIO()
|
||||
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
||||
foo_fd = io.BytesIO(json.dumps({'foo': 'bar'}, indent=2).encode())
|
||||
tarinfo = tarfile.TarInfo('foo.json')
|
||||
tarinfo.size = len(foo_fd.getvalue())
|
||||
tar.addfile(tarinfo, fileobj=foo_fd)
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-check/'), tar_io.getvalue())
|
||||
assert resp.json['err']
|
||||
assert resp.json['err_desc'] == 'Invalid tar file, missing manifest'
|
||||
|
||||
# missing component
|
||||
tar_io = io.BytesIO()
|
||||
with tarfile.open(mode='w', fileobj=tar_io) as tar:
|
||||
manifest_json = {
|
||||
'application': 'Test',
|
||||
'slug': 'test',
|
||||
'version_number': '42',
|
||||
'elements': [{'type': 'forms', 'slug': 'foo', 'name': 'foo'}],
|
||||
}
|
||||
manifest_fd = io.BytesIO(json.dumps(manifest_json, indent=2).encode())
|
||||
tarinfo = tarfile.TarInfo('manifest.json')
|
||||
tarinfo.size = len(manifest_fd.getvalue())
|
||||
tar.addfile(tarinfo, fileobj=manifest_fd)
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-check/'), tar_io.getvalue())
|
||||
assert resp.json['err']
|
||||
assert resp.json['err_desc'] == 'Invalid tar file, missing component forms/foo'
|
||||
|
||||
|
||||
def test_export_import_workflow_options(pub):
|
||||
FormDef.wipe()
|
||||
|
@ -1744,6 +2046,97 @@ def test_export_import_workflow_options(pub):
|
|||
assert formdef.workflow_options == {'foo': 'bar2'}
|
||||
|
||||
|
||||
def test_export_import_with_deprecated(pub):
|
||||
pub.load_site_options()
|
||||
if not pub.site_options.has_section('options'):
|
||||
pub.site_options.add_section('options')
|
||||
pub.site_options.set('options', 'forbid-new-python-expressions', 'true')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'foo'
|
||||
formdef.fields = [
|
||||
PageField(id='1', label='page1', condition={'type': 'python', 'value': 'True'}),
|
||||
]
|
||||
formdef.store()
|
||||
bundle = create_bundle(
|
||||
[
|
||||
{'type': 'forms', 'slug': 'foo', 'name': 'foo'},
|
||||
],
|
||||
('forms/foo', formdef),
|
||||
)
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-import/'), bundle)
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'failed'
|
||||
|
||||
blockdef = BlockDef()
|
||||
blockdef.name = 'foo'
|
||||
blockdef.fields = [
|
||||
StringField(id='2', label='python_prefill', prefill={'type': 'formula', 'value': '1 + 2'}),
|
||||
]
|
||||
blockdef.store()
|
||||
bundle = create_bundle(
|
||||
[
|
||||
{'type': 'blocks', 'slug': 'foo', 'name': 'foo'},
|
||||
],
|
||||
('blocks/foo', blockdef),
|
||||
)
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-import/'), bundle)
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'failed'
|
||||
|
||||
workflow = Workflow(name='foo')
|
||||
st0 = workflow.add_status('Status0', 'st0')
|
||||
sendsms = st0.add_action('sendsms', id='_sendsms')
|
||||
sendsms.to = 'xxx'
|
||||
sendsms.condition = {'type': 'python', 'value': 'True'}
|
||||
sendsms.parent = st0
|
||||
st0.items.append(sendsms)
|
||||
workflow.store()
|
||||
bundle = create_bundle(
|
||||
[
|
||||
{'type': 'workflows', 'slug': 'foo', 'name': 'foo'},
|
||||
],
|
||||
('workflows/foo', workflow),
|
||||
)
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-import/'), bundle)
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'failed'
|
||||
|
||||
data_source = NamedDataSource(name='foo')
|
||||
data_source.data_source = {'type': 'formula', 'value': repr([('1', 'un'), ('2', 'deux')])}
|
||||
data_source.store()
|
||||
bundle = create_bundle(
|
||||
[
|
||||
{'type': 'data-sources', 'slug': 'foo', 'name': 'foo'},
|
||||
],
|
||||
('data-sources/foo', data_source),
|
||||
)
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-import/'), bundle)
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'failed'
|
||||
|
||||
wscall = NamedWsCall()
|
||||
wscall.name = 'foo'
|
||||
wscall.request = {'url': 'http://example.net', 'qs_data': {'a': '=1+2'}}
|
||||
wscall.store()
|
||||
bundle = create_bundle(
|
||||
[
|
||||
{'type': 'wscalls', 'slug': 'foo', 'name': 'foo'},
|
||||
],
|
||||
('wscalls/foo', wscall),
|
||||
)
|
||||
resp = get_app(pub).put(sign_uri('/api/export-import/bundle-import/'), bundle)
|
||||
afterjob_url = resp.json['url']
|
||||
resp = get_app(pub).put(sign_uri(afterjob_url))
|
||||
assert resp.json['data']['status'] == 'failed'
|
||||
|
||||
|
||||
def test_api_export_import_invalid_slug(pub):
|
||||
pub.role_class.wipe()
|
||||
role1 = pub.role_class(name='Test role 1')
|
||||
|
|
|
@ -7,9 +7,11 @@ import re
|
|||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
import zipfile
|
||||
from contextlib import contextmanager
|
||||
|
||||
import pytest
|
||||
from django.utils.encoding import force_bytes
|
||||
from django.utils.timezone import localtime, make_aware
|
||||
from quixote import get_publisher
|
||||
from webtest import Upload
|
||||
|
||||
|
@ -106,6 +108,15 @@ def ics_data(local_user):
|
|||
formdata.store()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def low_export_limit_threshold():
|
||||
from wcs.backoffice.management import FormPage
|
||||
|
||||
FormPage.WCS_SYNC_EXPORT_LIMIT = 10
|
||||
yield
|
||||
FormPage.WCS_SYNC_EXPORT_LIMIT = 100
|
||||
|
||||
|
||||
@pytest.mark.parametrize('user', ['query-email', 'api-access'])
|
||||
@pytest.mark.parametrize('auth', ['signature', 'http-basic'])
|
||||
def test_formdata(pub, local_user, user, auth):
|
||||
|
@ -152,6 +163,7 @@ def test_formdata(pub, local_user, user, auth):
|
|||
block = BlockDef()
|
||||
block.name = 'foobar'
|
||||
block.fields = [
|
||||
fields.TitleField(id='dsd', label='Title'),
|
||||
fields.StringField(id='abc', label='Foo', varname='foo'),
|
||||
fields.ItemField(id='xyz', label='Test', data_source={'type': 'foobar'}, varname='bar'),
|
||||
]
|
||||
|
@ -291,7 +303,7 @@ def test_formdata(pub, local_user, user, auth):
|
|||
|
||||
# check status visibility
|
||||
workflow.add_status('Status1', 'st1')
|
||||
workflow.possible_status[-1].visibility = ['unknown']
|
||||
workflow.possible_status[-1].visibility = ['__hidden__']
|
||||
workflow.store()
|
||||
formdef.refresh_from_storage() # also update cached workflow
|
||||
formdata.jump_status('st1')
|
||||
|
@ -979,9 +991,14 @@ def test_api_list_formdata(pub, local_user):
|
|||
if i % 7 == 0:
|
||||
formdata.backoffice_submission = True
|
||||
formdata.submission_channel = 'mail'
|
||||
formdata.evolution[-1].time = (
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2018, 1, 2, 3, 4) + datetime.timedelta(hours=i))
|
||||
formdata.evolution[0].time = make_aware(
|
||||
datetime.datetime(2019, 1, 2, 3, 4) + datetime.timedelta(hours=i)
|
||||
)
|
||||
formdata.evolution[-1].time = make_aware(
|
||||
datetime.datetime(2020, 1, 2, 3, 4) + datetime.timedelta(hours=i)
|
||||
).timetuple()
|
||||
)
|
||||
formdata._store_all_evolution = True
|
||||
formdata.store()
|
||||
# a draft by user
|
||||
formdata = data_class()
|
||||
|
@ -1247,12 +1264,12 @@ def test_api_list_formdata_filter_status(pub, local_user):
|
|||
# filter on id
|
||||
resp = get_app(pub).get(sign_uri('/api/forms/foo/list?filter=new', user=local_user))
|
||||
assert len(resp.json) == 1
|
||||
assert resp.json[0]['id'] == new.id
|
||||
assert resp.json[0]['id'] == str(new.id)
|
||||
|
||||
# filter on name
|
||||
resp = get_app(pub).get(sign_uri('/api/forms/foo/list?filter=Ongoing', user=local_user))
|
||||
assert len(resp.json) == 1
|
||||
assert resp.json[0]['id'] == wip.id
|
||||
assert resp.json[0]['id'] == str(wip.id)
|
||||
|
||||
|
||||
def test_api_list_formdata_unknown_filter(pub, local_user):
|
||||
|
@ -1349,6 +1366,9 @@ def test_api_list_formdata_string_filter(pub, local_user):
|
|||
('existing', 'on', 3),
|
||||
('between', 'FOO 1|FOO 2', 1),
|
||||
('between', 'FOO 2|FOO 1', 1),
|
||||
('icontains', 'FOO', 3),
|
||||
('icontains', 'foo', 3),
|
||||
('icontains', '2', 1),
|
||||
]
|
||||
for operator, value, result in params:
|
||||
resp = get_app(pub).get(
|
||||
|
@ -1525,6 +1545,9 @@ def test_api_list_formdata_text_filter(pub, local_user):
|
|||
('existing', 'on', 3),
|
||||
('between', 'FOO 1|FOO 2', 1),
|
||||
('between', 'FOO 2|FOO 1', 1),
|
||||
('icontains', 'FOO', 3),
|
||||
('icontains', 'foo', 3),
|
||||
('icontains', '2', 1),
|
||||
]
|
||||
for operator, value, result in params:
|
||||
resp = get_app(pub).get(
|
||||
|
@ -1994,6 +2017,8 @@ def test_api_list_formdata_email_filter(pub, local_user):
|
|||
('not_in', 'a@localhost|b@localhost', 1),
|
||||
('absent', 'on', 2),
|
||||
('existing', 'on', 3),
|
||||
('icontains', 'A@LOCAL', 1),
|
||||
('icontains', 'C@LOCAL', 0),
|
||||
]
|
||||
for operator, value, result in params:
|
||||
resp = get_app(pub).get(
|
||||
|
@ -2290,6 +2315,8 @@ def test_api_list_formdata_block_field_filter(pub, local_user):
|
|||
('existing', 'on', 12),
|
||||
('between', 'plop1|plop5', 7),
|
||||
('between', 'plop5|plop1', 7),
|
||||
('icontains', 'PLOP', 12),
|
||||
('icontains', 'LOP1', 4), # plop1 (twice), plop10, plop11
|
||||
]
|
||||
for operator, value, result in params:
|
||||
resp = get_app(pub).get(
|
||||
|
@ -2554,7 +2581,7 @@ def test_api_anonymized_formdata(pub, local_user, admin_user):
|
|||
else:
|
||||
evo = Evolution(formdata=formdata)
|
||||
evo.who = admin_user.id
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
evo.status = 'wf-%s' % 'st2'
|
||||
formdata.evolution.append(evo)
|
||||
formdata.status = evo.status
|
||||
|
@ -2697,7 +2724,7 @@ def test_api_access_restrict_to_anonymised_data(pub, local_user, auth):
|
|||
sign_uri(url, user=local_user, orig=access.access_identifier, key=access.access_key), **kwargs
|
||||
)
|
||||
|
||||
resp = get_url('/api/forms/test/list?full=on')
|
||||
resp = get_url('/api/forms/test/list?full=on&order_by=id')
|
||||
assert len(resp.json) == 10
|
||||
assert resp.json[0]['fields']['foobar'] == 'FOO BAR1'
|
||||
assert resp.json[0]['fields']['foobar2'] == 'FOO BAR 2'
|
||||
|
@ -2715,7 +2742,7 @@ def test_api_access_restrict_to_anonymised_data(pub, local_user, auth):
|
|||
access.restrict_to_anonymised_data = True
|
||||
access.store()
|
||||
|
||||
resp = get_url('/api/forms/test/list?full=on')
|
||||
resp = get_url('/api/forms/test/list?full=on&order_by=id')
|
||||
assert len(resp.json) == 10
|
||||
assert 'foobar' not in resp.json[0]['fields']
|
||||
assert resp.json[0]['fields']['foobar2'] == 'FOO BAR 2'
|
||||
|
@ -2958,11 +2985,15 @@ def test_api_distance_filter(pub, local_user):
|
|||
get_app(pub).get(sign_uri('/api/forms/test/list?filter-distance=150000', user=local_user), status=400)
|
||||
|
||||
|
||||
def test_api_ods_formdata(pub, local_user):
|
||||
@pytest.mark.parametrize('user', ['query-email', 'api-access'])
|
||||
@pytest.mark.parametrize('auth', ['signature', 'http-basic'])
|
||||
def test_api_ods_formdata(pub, local_user, user, auth):
|
||||
pub.role_class.wipe()
|
||||
role = pub.role_class(name='test')
|
||||
role.store()
|
||||
|
||||
app = get_app(pub)
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test'
|
||||
|
@ -2975,10 +3006,36 @@ def test_api_ods_formdata(pub, local_user):
|
|||
data_class = formdef.data_class()
|
||||
data_class.wipe()
|
||||
|
||||
if user == 'api-access':
|
||||
ApiAccess.wipe()
|
||||
access = ApiAccess()
|
||||
access.name = 'test'
|
||||
access.access_identifier = 'test'
|
||||
access.access_key = '12345'
|
||||
access.store()
|
||||
|
||||
if auth == 'http-basic':
|
||||
|
||||
def get_url(url, **kwargs):
|
||||
app.set_authorization(('Basic', ('test', '12345')))
|
||||
return app.get(url, **kwargs)
|
||||
|
||||
else:
|
||||
|
||||
def get_url(url, **kwargs):
|
||||
return app.get(sign_uri(url, orig=access.access_identifier, key=access.access_key), **kwargs)
|
||||
|
||||
else:
|
||||
if auth == 'http-basic':
|
||||
pytest.skip('http basic authentication requires ApiAccess')
|
||||
|
||||
def get_url(url, **kwargs):
|
||||
return app.get(sign_uri(url, user=local_user), **kwargs)
|
||||
|
||||
# check access is denied if the user has not the appropriate role
|
||||
resp = get_app(pub).get(sign_uri('/api/forms/test/ods', user=local_user), status=403)
|
||||
resp = get_url('/api/forms/test/ods', status=403)
|
||||
# even if there's an anonymise parameter
|
||||
resp = get_app(pub).get(sign_uri('/api/forms/test/ods?anonymise', user=local_user), status=403)
|
||||
resp = get_url('/api/forms/test/ods?anonymise', status=403)
|
||||
|
||||
data = {'0': 'foobar'}
|
||||
for i in range(30):
|
||||
|
@ -2993,28 +3050,25 @@ def test_api_ods_formdata(pub, local_user):
|
|||
formdata.store()
|
||||
|
||||
# add proper role to user
|
||||
local_user.roles = [role.id]
|
||||
local_user.store()
|
||||
if user == 'api-access':
|
||||
access.roles = [role]
|
||||
access.store()
|
||||
else:
|
||||
local_user.roles = [role.id]
|
||||
local_user.store()
|
||||
|
||||
# check it gets the data
|
||||
resp = get_app(pub).get(sign_uri('/api/forms/test/ods', user=local_user))
|
||||
resp = get_url('/api/forms/test/ods')
|
||||
assert resp.content_type == 'application/vnd.oasis.opendocument.spreadsheet'
|
||||
|
||||
# check it still gives a ods file when there is more data
|
||||
for i in range(300):
|
||||
formdata = data_class()
|
||||
formdata.data = data
|
||||
formdata.user_id = local_user.id
|
||||
formdata.just_created()
|
||||
formdata.jump_status('new')
|
||||
formdata.store()
|
||||
|
||||
resp = get_app(pub).get(sign_uri('/api/forms/test/ods', user=local_user))
|
||||
assert resp.content_type == 'application/vnd.oasis.opendocument.spreadsheet'
|
||||
with zipfile.ZipFile(io.BytesIO(resp.body)) as zipf:
|
||||
with zipf.open('content.xml') as fd:
|
||||
ods_sheet = ET.parse(fd)
|
||||
assert len(ods_sheet.findall('.//{%s}table-row' % ods.NS['table'])) == 311
|
||||
# check it still gives a ods file when it's over the threashold for afterjobs
|
||||
with low_export_limit_threshold():
|
||||
resp = get_url('/api/forms/test/ods')
|
||||
assert resp.content_type == 'application/vnd.oasis.opendocument.spreadsheet'
|
||||
with zipfile.ZipFile(io.BytesIO(resp.body)) as zipf:
|
||||
with zipf.open('content.xml') as fd:
|
||||
ods_sheet = ET.parse(fd)
|
||||
assert len(ods_sheet.findall('.//{%s}table-row' % ods.NS['table'])) == 11
|
||||
|
||||
# check it's not subject to category permissions
|
||||
role2 = pub.role_class(name='test2')
|
||||
|
@ -3025,7 +3079,7 @@ def test_api_ods_formdata(pub, local_user):
|
|||
category.store()
|
||||
formdef.category = category
|
||||
formdef.store()
|
||||
get_app(pub).get(sign_uri('/api/forms/test/ods', user=local_user), status=200)
|
||||
get_url('/api/forms/test/ods', status=200)
|
||||
|
||||
|
||||
def test_api_global_geojson(pub, local_user):
|
||||
|
@ -3360,7 +3414,7 @@ def test_api_include_anonymised(pub, local_user):
|
|||
formdata.anonymise()
|
||||
|
||||
resp = get_app(pub).get(sign_uri('/api/forms/', user=local_user))
|
||||
assert len(resp.json['data']) == 10
|
||||
assert len(resp.json['data']) == 9
|
||||
|
||||
resp = get_app(pub).get(sign_uri('/api/forms/?include-anonymised=on', user=local_user))
|
||||
assert len(resp.json['data']) == 10
|
||||
|
|
|
@ -10,6 +10,7 @@ from functools import partial
|
|||
import pytest
|
||||
import responses
|
||||
from django.utils.encoding import force_str
|
||||
from django.utils.timezone import localtime
|
||||
from quixote import get_publisher
|
||||
|
||||
from wcs import fields, qommon
|
||||
|
@ -163,7 +164,7 @@ def test_formdef_list(pub):
|
|||
formdata = formdef.data_class()()
|
||||
formdata.data = {}
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = (datetime.datetime.now() - datetime.timedelta(days=days)).timetuple()
|
||||
formdata.receipt_time = localtime() - datetime.timedelta(days=days)
|
||||
formdata.store()
|
||||
|
||||
resp = get_app(pub).get(sign_uri('/api/formdefs/?include-count=on'))
|
||||
|
@ -1222,6 +1223,8 @@ def test_formdef_import_export_unnamed_block(pub, admin_user):
|
|||
formdata_export = formdata.get_json_export_dict(include_unnamed_fields=True, include_evolution=False)
|
||||
del formdata_export['receipt_time']
|
||||
del formdata_export['last_update_time']
|
||||
del formdata_export['workflow']['real_status']['first_arrival_datetime']
|
||||
del formdata_export['workflow']['real_status']['latest_arrival_datetime']
|
||||
formdef.data_class().wipe()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
|
|
@ -3,6 +3,7 @@ import json
|
|||
import os
|
||||
|
||||
import pytest
|
||||
from django.utils.timezone import make_aware
|
||||
|
||||
from wcs import fields
|
||||
from wcs.backoffice.management import format_time
|
||||
|
@ -313,7 +314,7 @@ def test_statistics_forms_count(pub):
|
|||
for i in range(20):
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
# "Web" channel has three equivalent values
|
||||
if i == 0:
|
||||
formdata.submission_channel = 'web'
|
||||
|
@ -327,14 +328,14 @@ def test_statistics_forms_count(pub):
|
|||
for i in range(30):
|
||||
formdata = formdef2.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 3, 1, 2, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 3, 1, 2, 0))
|
||||
formdata.backoffice_submission = bool(i % 3)
|
||||
formdata.submission_channel = 'mail'
|
||||
formdata.store()
|
||||
|
||||
# draft should not be counted
|
||||
formdata = formdef.data_class()()
|
||||
formdata.receipt_time = datetime.datetime(2021, 3, 1, 2, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 3, 1, 2, 0))
|
||||
formdata.status = 'draft'
|
||||
formdata.store()
|
||||
|
||||
|
@ -414,7 +415,7 @@ def test_statistics_forms_count_subfilters(pub, formdef):
|
|||
formdata.data['3_display'] = 'Foo' if i % 2 else 'Bar, Baz'
|
||||
formdata.data['4'] = {'data': [{'2': ['foo', 'bar'], '2_display': 'Foo, Bar'}]}
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.store()
|
||||
|
||||
url = '/api/statistics/forms/count/?form=%s&time_interval=year' % formdef.url_name
|
||||
|
@ -568,6 +569,7 @@ def test_statistics_forms_count_subfilters(pub, formdef):
|
|||
|
||||
# remove fields and statuses
|
||||
workflow = Workflow(name='Empty wf')
|
||||
workflow.add_status('New')
|
||||
workflow.store()
|
||||
formdef.workflow = workflow
|
||||
formdef.fields.clear()
|
||||
|
@ -577,8 +579,31 @@ def test_statistics_forms_count_subfilters(pub, formdef):
|
|||
resp = get_app(pub).get(sign_uri(url))
|
||||
assert resp.json['data'] == {
|
||||
'series': [{'data': [], 'label': 'Forms Count'}],
|
||||
'subfilters': [],
|
||||
'x_labels': [],
|
||||
'subfilters': [
|
||||
{
|
||||
'has_subfilters': True,
|
||||
'id': 'group-by',
|
||||
'label': 'Group by',
|
||||
'options': [
|
||||
{'id': 'channel', 'label': 'Channel'},
|
||||
{'id': 'simple-status', 'label': 'Simplified status'},
|
||||
{'id': 'status', 'label': 'Status'},
|
||||
],
|
||||
},
|
||||
{
|
||||
'default': '_all',
|
||||
'id': 'filter-status',
|
||||
'label': 'Status',
|
||||
'options': [
|
||||
{'id': '_all', 'label': 'All'},
|
||||
{'id': 'pending', 'label': 'Open'},
|
||||
{'id': 'done', 'label': 'Done'},
|
||||
{'id': '1', 'label': 'New'},
|
||||
],
|
||||
'required': True,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
|
@ -593,7 +618,7 @@ def test_statistics_forms_count_subfilters_empty_block_items_field(pub, formdef)
|
|||
formdata = formdef.data_class()()
|
||||
formdata.data['4'] = {'data': [{'1': 'a', '1_display': 'B'}]}
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.store()
|
||||
|
||||
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=%s' % formdef.url_name))
|
||||
|
@ -619,7 +644,7 @@ def test_statistics_forms_count_subfilters_empty_item_field_no_datasource(pub, f
|
|||
formdata.just_created()
|
||||
formdata.data['10'] = 'extra-option'
|
||||
formdata.data['10_display'] = 'Extra option'
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.store()
|
||||
|
||||
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=%s' % formdef.url_name))
|
||||
|
@ -647,7 +672,7 @@ def test_statistics_forms_count_subfilters_query(pub, formdef):
|
|||
formdata.data['3'] = ['baz']
|
||||
formdata.data['4'] = {'data': [{'1': False, '2': ['foo', 'bar'], '2_display': 'Foo, Bar'}]}
|
||||
formdata.jump_status('2')
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.store()
|
||||
|
||||
# query all formdata
|
||||
|
@ -769,7 +794,7 @@ def test_statistics_forms_count_subfilters_query_same_varname(pub, formdef):
|
|||
for i in range(5):
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
if i == 0:
|
||||
formdata.data['1'] = 'foo'
|
||||
if i == 1:
|
||||
|
@ -803,7 +828,7 @@ def test_statistics_forms_count_subfilters_query_integer_items(pub, formdef):
|
|||
formdata.data['3'] = ['1', '2']
|
||||
else:
|
||||
formdata.data['3'] = ['1']
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.store()
|
||||
|
||||
url = '/api/statistics/forms/count/?form=%s' % formdef.url_name
|
||||
|
@ -819,7 +844,7 @@ def test_statistics_forms_count_group_by(pub, formdef, anonymise):
|
|||
for i in range(20):
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
if i % 3:
|
||||
formdata.data['1'] = True
|
||||
formdata.data['2'] = 'foo'
|
||||
|
@ -855,7 +880,7 @@ def test_statistics_forms_count_group_by(pub, formdef, anonymise):
|
|||
formdata.submission_channel = 'mail'
|
||||
formdata.backoffice_submission = bool(i % 3)
|
||||
else:
|
||||
formdata.receipt_time = datetime.datetime(2021, 3, 1, 2, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 3, 1, 2, 0))
|
||||
formdata.store()
|
||||
if anonymise:
|
||||
formdata.anonymise()
|
||||
|
@ -966,6 +991,10 @@ def test_statistics_forms_count_group_by(pub, formdef, anonymise):
|
|||
{'data': [6, None, None], 'label': 'Backoffice'},
|
||||
]
|
||||
|
||||
# group by channel without form filter
|
||||
new_resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?group-by=channel'))
|
||||
assert new_resp.json['data']['series'] == resp.json['data']['series']
|
||||
|
||||
# group by item field without time interval
|
||||
resp = get_app(pub).get(sign_uri(url + '&group-by=test-item&time_interval=none'))
|
||||
# Foo is first because it has a display value, baz is second because it has not, None is always last
|
||||
|
@ -1017,7 +1046,7 @@ def test_statistics_forms_count_group_by_same_varname(pub, formdef):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = 'foo'
|
||||
formdata.data['2'] = 'bar'
|
||||
formdata.store()
|
||||
|
@ -1036,7 +1065,7 @@ def test_statistics_forms_count_group_by_same_varname(pub, formdef):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['2'] = 'foo'
|
||||
formdata.store()
|
||||
|
||||
|
@ -1045,24 +1074,29 @@ def test_statistics_forms_count_group_by_same_varname(pub, formdef):
|
|||
|
||||
|
||||
def test_statistics_forms_count_group_by_form(pub):
|
||||
category_a = Category(name='Category A')
|
||||
category_a.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'A'
|
||||
formdef.category_id = category_a.id
|
||||
formdef.store()
|
||||
|
||||
for i in range(10):
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2022, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2022, 1, 1, 0, 0))
|
||||
formdata.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'B'
|
||||
formdef.category_id = category_a.id
|
||||
formdef.store()
|
||||
|
||||
for i in range(5):
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.store()
|
||||
|
||||
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/'))
|
||||
|
@ -1070,7 +1104,11 @@ def test_statistics_forms_count_group_by_form(pub):
|
|||
assert resp.json['data']['subfilters'][1] == {
|
||||
'id': 'group-by',
|
||||
'label': 'Group by',
|
||||
'options': [{'id': 'form', 'label': 'Form'}],
|
||||
'options': [
|
||||
{'id': 'channel', 'label': 'Channel'},
|
||||
{'id': 'form', 'label': 'Form'},
|
||||
],
|
||||
'has_subfilters': True,
|
||||
}
|
||||
|
||||
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?time_interval=year'))
|
||||
|
@ -1088,6 +1126,18 @@ def test_statistics_forms_count_group_by_form(pub):
|
|||
assert resp.json['data']['x_labels'] == ['A', 'B']
|
||||
assert resp.json['data']['series'] == [{'data': [10, 5], 'label': 'Forms Count'}]
|
||||
|
||||
resp = get_app(pub).get(
|
||||
sign_uri('/api/statistics/forms/count/?time_interval=none&group-by=form&form=category:category-a')
|
||||
)
|
||||
assert resp.json['data']['x_labels'] == ['A', 'B']
|
||||
assert resp.json['data']['series'] == [{'data': [10, 5], 'label': 'Forms Count'}]
|
||||
|
||||
resp = get_app(pub).get(
|
||||
sign_uri('/api/statistics/forms/count/?time_interval=none&group-by=form&form=a&form=b')
|
||||
)
|
||||
assert resp.json['data']['x_labels'] == ['A', 'B']
|
||||
assert resp.json['data']['series'] == [{'data': [10, 5], 'label': 'Forms Count'}]
|
||||
|
||||
|
||||
def test_statistics_forms_count_months_to_show(pub, formdef):
|
||||
for i in range(24):
|
||||
|
@ -1095,7 +1145,7 @@ def test_statistics_forms_count_months_to_show(pub, formdef):
|
|||
formdata.data['2'] = 'foo' if i % 2 else 'baz'
|
||||
formdata.data['2_display'] = 'Foo' if i % 2 else 'Baz'
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2022 + i // 12, i % 12 + 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2022 + i // 12, i % 12 + 1, 1, 0, 0))
|
||||
formdata.store()
|
||||
|
||||
url = '/api/statistics/forms/count/'
|
||||
|
@ -1140,7 +1190,7 @@ def test_statistics_cards_count(pub):
|
|||
for _i in range(20):
|
||||
carddata = carddef.data_class()()
|
||||
carddata.just_created()
|
||||
carddata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
carddata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
carddata.store()
|
||||
|
||||
# apply (required) card filter
|
||||
|
@ -1399,8 +1449,8 @@ def test_statistics_resolution_time_median(pub, freezer):
|
|||
resp = get_app(pub).get(sign_uri('/api/statistics/resolution-time/?form=test'))
|
||||
assert get_humanized_duration_serie(resp.json) == [
|
||||
'1 day(s) and 0 hour(s)', # min
|
||||
'89 day(s) and 23 hour(s)', # max
|
||||
'13 day(s) and 23 hour(s)', # mean
|
||||
'90 day(s) and 0 hour(s)', # max
|
||||
'14 day(s) and 0 hour(s)', # mean
|
||||
'5 day(s) and 0 hour(s)', # median
|
||||
]
|
||||
|
||||
|
@ -1511,7 +1561,7 @@ def test_statistics_multiple_forms_count(pub, formdef):
|
|||
formdata.data['3'] = ['foo']
|
||||
formdata.data['3_display'] = 'Foo'
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.store()
|
||||
|
||||
for _i in range(30):
|
||||
|
@ -1522,7 +1572,7 @@ def test_statistics_multiple_forms_count(pub, formdef):
|
|||
formdata.data['3_display'] = 'Bar, Baz'
|
||||
formdata.data['4'] = {'data': [{'1': True}]}
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 3, 1, 2, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 3, 1, 2, 0))
|
||||
formdata.jump_status('2')
|
||||
formdata.store()
|
||||
|
||||
|
@ -1622,14 +1672,14 @@ def test_statistics_multiple_forms_count_different_ids(pub):
|
|||
formdata.data['1'] = 'foo'
|
||||
formdata.data['1_display'] = 'Foo'
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.store()
|
||||
|
||||
formdata = formdef2.data_class()()
|
||||
formdata.data['2'] = 'baz'
|
||||
formdata.data['2_display'] = 'Baz'
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 3, 1, 2, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 3, 1, 2, 0))
|
||||
formdata.store()
|
||||
|
||||
url = '/api/statistics/forms/count/?form=%s&form=%s' % (formdef1.url_name, formdef2.url_name)
|
||||
|
@ -1664,7 +1714,7 @@ def test_statistics_multiple_forms_count_subfilters(pub, formdef):
|
|||
formdata.data['2'] = 'foo'
|
||||
formdata.data['2_display'] = 'Foo'
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.jump_status('2')
|
||||
formdata.store()
|
||||
|
||||
|
@ -1673,7 +1723,7 @@ def test_statistics_multiple_forms_count_subfilters(pub, formdef):
|
|||
formdata.data['2'] = 'baz'
|
||||
formdata.data['2_display'] = 'Baz'
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 3, 1, 2, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 3, 1, 2, 0))
|
||||
formdata.store()
|
||||
|
||||
resp = get_app(pub).get(
|
||||
|
@ -1684,6 +1734,7 @@ def test_statistics_multiple_forms_count_subfilters(pub, formdef):
|
|||
group_by_filter = [x for x in resp.json['data']['subfilters'] if x['id'] == 'group-by'][0]
|
||||
assert group_by_filter['options'] == [
|
||||
{'id': 'channel', 'label': 'Channel'},
|
||||
{'id': 'form', 'label': 'Form'},
|
||||
{'id': 'simple-status', 'label': 'Simplified status'},
|
||||
{'id': 'test-item', 'label': 'Test item'},
|
||||
{'id': 'checkbox', 'label': 'Checkbox'},
|
||||
|
@ -1707,3 +1758,10 @@ def test_statistics_multiple_forms_count_subfilters(pub, formdef):
|
|||
|
||||
category_resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=category:category-a'))
|
||||
assert category_resp.json == resp.json
|
||||
|
||||
# cannot group by form if single form is selected
|
||||
form_resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=test'))
|
||||
form_group_by_filter = [x for x in form_resp.json['data']['subfilters'] if x['id'] == 'group-by'][0]
|
||||
assert [x for x in group_by_filter['options'] if x not in form_group_by_filter['options']] == [
|
||||
{'id': 'form', 'label': 'Form'}
|
||||
]
|
||||
|
|
|
@ -3,6 +3,7 @@ import os
|
|||
from functools import partial
|
||||
|
||||
import pytest
|
||||
from django.utils.timezone import make_aware
|
||||
from quixote import get_publisher
|
||||
|
||||
from wcs import fields
|
||||
|
@ -377,7 +378,7 @@ def test_user_forms(pub, local_user, access):
|
|||
formdata = formdef.data_class()()
|
||||
formdata.user_id = local_user.id
|
||||
formdata.status = 'draft'
|
||||
formdata.receipt_time = datetime.datetime(2015, 1, 1).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2015, 1, 1))
|
||||
formdata.store()
|
||||
|
||||
resp = get_app(pub).get(sign_uri('/api/user/forms', user=local_user))
|
||||
|
@ -399,7 +400,7 @@ def test_user_forms(pub, local_user, access):
|
|||
formdata.data = {'0': 'foo@localhost', '1': 'xyy'}
|
||||
formdata.user_id = local_user.id
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = (datetime.datetime.now() + datetime.timedelta(days=1)).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime.now() + datetime.timedelta(days=1))
|
||||
formdata.jump_status('new')
|
||||
formdata.store()
|
||||
|
||||
|
@ -408,9 +409,9 @@ def test_user_forms(pub, local_user, access):
|
|||
resp2 = get_app(pub).get(sign_uri('/api/user/forms?sort=desc', user=local_user))
|
||||
assert len(resp2.json['data']) == 4
|
||||
assert resp2.json['data'][0] == resp.json['data'][3]
|
||||
assert resp2.json['data'][1] == resp.json['data'][0]
|
||||
assert resp2.json['data'][1] == resp.json['data'][2]
|
||||
assert resp2.json['data'][2] == resp.json['data'][1]
|
||||
assert resp2.json['data'][3] == resp.json['data'][2]
|
||||
assert resp2.json['data'][3] == resp.json['data'][0]
|
||||
|
||||
# check there is no access with roles-limited API users
|
||||
role = pub.role_class(name='test')
|
||||
|
@ -443,7 +444,7 @@ def test_user_forms_limit_offset(pub, local_user):
|
|||
formdata.data = {'0': 'foo@localhost', '1': str(i)}
|
||||
formdata.user_id = local_user.id
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = (datetime.datetime.now() + datetime.timedelta(days=i)).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime.now() + datetime.timedelta(days=i))
|
||||
formdata.jump_status('new')
|
||||
formdata.store()
|
||||
|
||||
|
@ -452,7 +453,7 @@ def test_user_forms_limit_offset(pub, local_user):
|
|||
formdata.data = {'0': 'foo@localhost', '1': str(i)}
|
||||
formdata.user_id = local_user.id
|
||||
formdata.status = 'draft'
|
||||
formdata.receipt_time = (datetime.datetime.now() - datetime.timedelta(days=i)).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime.now() - datetime.timedelta(days=i))
|
||||
formdata.store()
|
||||
|
||||
resp = get_app(pub).get(sign_uri('/api/users/%s/forms' % local_user.id))
|
||||
|
@ -772,7 +773,7 @@ def test_user_drafts(pub, local_user):
|
|||
formdata.user_id = local_user.id
|
||||
formdata.page_no = 1
|
||||
formdata.status = 'draft'
|
||||
formdata.receipt_time = datetime.datetime(2015, 1, 1).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2015, 1, 1))
|
||||
formdata.store()
|
||||
|
||||
resp = get_app(pub).get(sign_uri('/api/user/drafts', user=local_user))
|
||||
|
|
|
@ -9,6 +9,7 @@ import zipfile
|
|||
|
||||
import pytest
|
||||
import responses
|
||||
from django.utils.timezone import make_aware
|
||||
from webtest import Upload
|
||||
|
||||
import wcs.qommon.storage as st
|
||||
|
@ -24,6 +25,7 @@ from wcs.qommon.ident.password_accounts import PasswordAccount
|
|||
from wcs.qommon.upload_storage import PicklableUpload
|
||||
from wcs.roles import logged_users_role
|
||||
from wcs.sql_criterias import Contains
|
||||
from wcs.wf.comment import WorkflowCommentPart
|
||||
from wcs.wf.create_formdata import Mapping
|
||||
from wcs.wf.form import WorkflowFormEvolutionPart, WorkflowFormFieldsFormDef
|
||||
from wcs.wf.register_comment import JournalEvolutionPart
|
||||
|
@ -131,7 +133,7 @@ def create_environment(pub, set_receiver=True):
|
|||
for i in range(50):
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2015, 1, 1, 0, i).timetuple()
|
||||
formdata.receipt_time = datetime.datetime(2015, 1, 1, 0, i)
|
||||
formdata.data = {'1': 'FOO BAR %d' % i}
|
||||
if i % 4 == 0:
|
||||
formdata.data[formdef.fields[1].id] = 'foo'
|
||||
|
@ -169,7 +171,7 @@ def create_environment(pub, set_receiver=True):
|
|||
for i in range(20):
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2014, 1, 1).timetuple()
|
||||
formdata.receipt_time = datetime.datetime(2014, 1, 1)
|
||||
formdata.jump_status('new')
|
||||
formdata.store()
|
||||
|
||||
|
@ -537,7 +539,6 @@ def test_backoffice_listing_fts(pub):
|
|||
assert resp.text.count('data-link') == 17
|
||||
resp = app.get('/backoffice/management/form-title/')
|
||||
resp.forms['listing-settings']['filter'] = 'all'
|
||||
resp.forms['listing-settings']['q'] = 'foo'
|
||||
resp.forms['listing-settings']['limit'] = '100'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert resp.pyquery('tbody tr').length == 50
|
||||
|
@ -545,9 +546,29 @@ def test_backoffice_listing_fts(pub):
|
|||
'%s-%s' % (formdef.id, i) for i in range(50, 0, -1)
|
||||
]
|
||||
|
||||
# search on text (foo is on all formdata so it gets the same set of results, but ordered differently)
|
||||
resp.forms['listing-settings']['q'] = 'foo'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert resp.pyquery('tbody tr').length == 50
|
||||
assert {x.text for x in resp.pyquery('tbody tr .cell-id a')} == {
|
||||
'%s-%s' % (formdef.id, i) for i in range(50, 0, -1)
|
||||
} # same set
|
||||
assert [x.text for x in resp.pyquery('tbody tr .cell-id a')] != [
|
||||
'%s-%s' % (formdef.id, i) for i in range(50, 0, -1)
|
||||
] # but different order
|
||||
# get first row, check it has b'foo' in its item field
|
||||
formdata = formdef.data_class().get(resp.pyquery('tbody tr .cell-id a')[0].attrib['href'].strip('/'))
|
||||
assert formdata.data[formdef.fields[1].id] == 'foo'
|
||||
|
||||
resp.forms['listing-settings']['q'] = 'baz'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert resp.pyquery('tbody tr').length == 24
|
||||
results = [x.text for x in resp.pyquery('tbody tr .cell-id a')]
|
||||
# force order, check it's same set but different order
|
||||
resp.forms['listing-settings']['order_by'] = '-receipt_time'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert {x.text for x in resp.pyquery('tbody tr .cell-id a')} == set(results)
|
||||
assert [x.text for x in resp.pyquery('tbody tr .cell-id a')] != results
|
||||
|
||||
|
||||
def test_backoffice_legacy_urls(pub):
|
||||
|
@ -1239,7 +1260,9 @@ def test_backoffice_multi_actions_interactive(pub):
|
|||
for i in range(10):
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2015, 1, 1, 0, i))
|
||||
formdata.jump_status('new')
|
||||
formdata.evolution[-1].time = make_aware(datetime.datetime(2015, 1, 1, 0, i))
|
||||
formdata.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
@ -1669,6 +1692,37 @@ def test_backoffice_handling(pub):
|
|||
assert 'HELLO WORLD' in resp.text
|
||||
|
||||
|
||||
def test_backoffice_parallel_handling(pub, freezer):
|
||||
create_user(pub)
|
||||
create_environment(pub)
|
||||
form_class = FormDef.get_by_urlname('form-title').data_class()
|
||||
number31 = [x for x in form_class.select() if x.data['1'] == 'FOO BAR 30'][0]
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/management/form-title/')
|
||||
assert re.findall(r'<tbody.*\/tbody>', resp.text, re.DOTALL)[0].count('<tr') == 17
|
||||
|
||||
# open formdata twice
|
||||
resp2 = resp.click(href='%s/' % number31.id)
|
||||
resp3 = resp.click(href='%s/' % number31.id)
|
||||
|
||||
freezer.move_to(datetime.timedelta(seconds=10))
|
||||
resp2.forms[0]['comment'] = 'HELLO WORLD'
|
||||
resp2 = resp2.forms[0].submit('button_accept')
|
||||
resp2 = resp2.follow()
|
||||
|
||||
resp3.forms[0]['comment'] = 'HELLO WORLD'
|
||||
resp3 = resp3.forms[0].submit('button_accept')
|
||||
assert resp3.pyquery('.global-errors summary').text() == 'Error: parallel execution.'
|
||||
assert (
|
||||
resp3.pyquery('.global-errors summary + p').text()
|
||||
== 'Another action has been performed on this form in the meantime and data may have been changed.'
|
||||
)
|
||||
# check it's possible to click it now, as it's been refreshed and the agent warned.
|
||||
assert 'button_finish' in resp3.forms[0].fields
|
||||
resp3 = resp3.forms[0].submit('button_finish').follow()
|
||||
assert not resp3.pyquery('.global-errors summary')
|
||||
|
||||
|
||||
def test_backoffice_handling_global_action(pub):
|
||||
create_user(pub)
|
||||
|
||||
|
@ -1705,6 +1759,41 @@ def test_backoffice_handling_global_action(pub):
|
|||
assert formdef.data_class().get(formdata.id).status == 'wf-finished'
|
||||
|
||||
|
||||
def test_backoffice_handling_global_action_parallel_handling(pub):
|
||||
create_user(pub)
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test global action'
|
||||
formdef.fields = []
|
||||
|
||||
workflow = Workflow(name='test global action')
|
||||
workflow.add_status('st0')
|
||||
action = workflow.add_global_action('FOOBAR')
|
||||
register_comment = action.add_action('register-comment')
|
||||
register_comment.comment = 'HELLO WORLD GLOBAL ACTION'
|
||||
jump = action.add_action('jump')
|
||||
jump.status = 'finished'
|
||||
trigger = action.triggers[0]
|
||||
trigger.roles = [x.id for x in pub.role_class.select() if x.name == 'foobar']
|
||||
|
||||
workflow.store()
|
||||
formdef.workflow_id = workflow.id
|
||||
formdef.workflow_roles = {'_receiver': 1}
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/management/%s/%s/' % (formdef.url_name, formdata.id))
|
||||
resp.form.submit('button-action-1').follow()
|
||||
resp3 = resp.form.submit('button-action-1')
|
||||
assert resp3.pyquery('.global-errors summary').text() == 'Error: parallel execution.'
|
||||
resp = app.get('/backoffice/management/%s/%s/' % (formdef.url_name, formdata.id))
|
||||
assert resp.text.count('HELLO WORLD GLOBAL ACTION') == 1
|
||||
|
||||
|
||||
def test_backoffice_global_remove_action(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
|
@ -2095,7 +2184,7 @@ def test_backoffice_download_as_zip(pub):
|
|||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
|
||||
assert 'Download all files as .zip' not in resp
|
||||
formdef.include_download_all_button = True
|
||||
formdef.management_sidebar_items.add('download-files')
|
||||
formdef.store()
|
||||
resp = app.get('/backoffice/management/form-title/%s/' % number31.id)
|
||||
resp = resp.click('Download all files as .zip')
|
||||
|
@ -2175,6 +2264,48 @@ def test_backoffice_geolocation_info(pub):
|
|||
assert 'data-init-lat="48.83' in resp.text
|
||||
|
||||
|
||||
def test_backoffice_sidebar_elements(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.geolocations = {'base': 'Geolocation'}
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.geolocations = {'base': {'lat': 48.83, 'lon': 2.32}}
|
||||
formdata.user_id = user.id
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
other_formdata = formdef.data_class()()
|
||||
other_formdata.just_created()
|
||||
other_formdata.just_created()
|
||||
other_formdata.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get(formdata.get_backoffice_url())
|
||||
assert [x.text for x in resp.pyquery('#sidebar .extra-context h3')] == [
|
||||
'General Information',
|
||||
'Associated User',
|
||||
'Geolocation',
|
||||
]
|
||||
assert len(resp.pyquery('[data-async-url$="/user-pending-forms"]')) == 1
|
||||
|
||||
formdef.management_sidebar_items = ['general', 'pending-forms']
|
||||
formdef.store()
|
||||
resp = app.get(formdata.get_backoffice_url())
|
||||
assert [x.text for x in resp.pyquery('#sidebar .extra-context h3')] == ['General Information']
|
||||
assert len(resp.pyquery('[data-async-url$="/user-pending-forms"]')) == 1
|
||||
|
||||
formdef.management_sidebar_items = ['geolocation']
|
||||
formdef.store()
|
||||
resp = app.get(formdata.get_backoffice_url())
|
||||
assert [x.text for x in resp.pyquery('#sidebar .extra-context h3')] == ['Geolocation']
|
||||
assert len(resp.pyquery('[data-async-url$="/user-pending-forms"]')) == 0
|
||||
|
||||
|
||||
def test_backoffice_info_text(pub):
|
||||
create_user(pub)
|
||||
create_environment(pub)
|
||||
|
@ -3149,6 +3280,57 @@ def test_backoffice_wfedit_single_page(pub):
|
|||
assert formdata.data == {'2': 'a', '4': 'changed', '5': None, '7': 'c'}
|
||||
|
||||
|
||||
def test_backoffice_wfedit_partial_pages(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
workflow = Workflow(name='test')
|
||||
st1 = workflow.add_status('Status1', 'st1')
|
||||
editable = st1.add_action('editable', id='_editable')
|
||||
editable.by = ['_receiver']
|
||||
editable.operation_mode = 'partial'
|
||||
editable.page_identifier = 'plop'
|
||||
workflow.store()
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test'
|
||||
formdef.fields = []
|
||||
formdef.store()
|
||||
|
||||
formdef.data_class().wipe()
|
||||
formdef.fields = [
|
||||
fields.PageField(id='1', label='1st page'),
|
||||
fields.StringField(id='2', label='field1'),
|
||||
fields.PageField(id='3', label='2nd page', varname='plop'),
|
||||
fields.StringField(id='4', label='field2'),
|
||||
fields.StringField(
|
||||
id='5', label='field2b', condition={'type': 'django', 'value': 'not is_in_backoffice'}
|
||||
),
|
||||
fields.PageField(id='6', label='3rd page'),
|
||||
fields.StringField(id='7', label='field3'),
|
||||
]
|
||||
formdef.workflow_id = workflow.id
|
||||
formdef.workflow_roles = {'_receiver': user.roles[0]}
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'2': 'a', '4': 'b', '5': 'b2', '7': 'c'}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get(formdata.get_backoffice_url())
|
||||
resp = resp.form.submit('button_editable').follow()
|
||||
assert [x.text for x in resp.pyquery('#steps .wcs-step--label-text')] == ['2nd page', '3rd page']
|
||||
resp.form['f4'] = 'changed'
|
||||
resp = resp.form.submit('submit')
|
||||
resp.form['f7'] = 'changed'
|
||||
resp = resp.form.submit('submit')
|
||||
formdata.refresh_from_storage()
|
||||
assert formdata.data == {'2': 'a', '4': 'changed', '5': None, '7': 'changed'}
|
||||
|
||||
|
||||
def test_global_listing(pub):
|
||||
create_user(pub)
|
||||
create_environment(pub)
|
||||
|
@ -3182,7 +3364,7 @@ def test_global_listing(pub):
|
|||
last_update_time = formdef.data_class().select(lambda x: not x.is_draft())[0].last_update_time
|
||||
# check created and last modified columns
|
||||
assert '>2014-01-01 00:00<' in resp.text
|
||||
assert time.strftime('>%Y-%m-%d', last_update_time) in resp.text
|
||||
assert f'>{last_update_time.strftime("%Y-%m-%d")}' in resp.text
|
||||
|
||||
# check digest is included
|
||||
formdata = formdef.data_class().get(
|
||||
|
@ -3707,6 +3889,13 @@ def test_formdata_lookup(pub):
|
|||
resp = resp.follow()
|
||||
assert 'No such tracking code or identifier.' in resp.text
|
||||
|
||||
# check it's not possible to replace back value with anything else
|
||||
for invalid_value in ('http://example.invalid/', 'xxx'):
|
||||
resp = app.get('/backoffice/management/listing')
|
||||
resp.forms[0]['back'] = invalid_value
|
||||
resp = resp.forms[0].submit()
|
||||
assert resp.location == 'http://example.net/backoffice/management/'
|
||||
|
||||
|
||||
def test_backoffice_sidebar_user_context(pub):
|
||||
user = create_user(pub)
|
||||
|
@ -4941,7 +5130,8 @@ def test_backoffice_logged_errors(pub):
|
|||
assert 'ZeroDivisionError' in resp2.text
|
||||
resp = resp2.click('Failed to evaluate condition')
|
||||
assert 'ZeroDivisionError: integer division or modulo by zero' in resp.text
|
||||
assert 'Python Expression: <code>1//0</code>' in resp.text
|
||||
assert 'Condition: <code>1//0</code>' in resp.text
|
||||
assert 'Condition type: <code>python</code>' in resp.text
|
||||
resp = resp.click('Delete').follow()
|
||||
assert pub.loggederror_class.count() == 0
|
||||
|
||||
|
@ -5319,322 +5509,6 @@ def test_lazy_eval_with_conditional_workflow_form(pub):
|
|||
assert context['form_var_foo_bar'] == 'go'
|
||||
|
||||
|
||||
@pytest.fixture(params=[{'attach_to_history': True}, {}])
|
||||
def create_formdata(request, pub):
|
||||
admin = create_user(pub, is_admin=True)
|
||||
|
||||
FormDef.wipe()
|
||||
|
||||
source_formdef = FormDef()
|
||||
source_formdef.name = 'source form'
|
||||
source_formdef.workflow_roles = {'_receiver': 1}
|
||||
source_formdef.fields = [
|
||||
fields.StringField(id='0', label='string', varname='toto_string'),
|
||||
fields.FileField(id='1', label='file', varname='toto_file'),
|
||||
]
|
||||
source_formdef.store()
|
||||
|
||||
target_formdef = FormDef()
|
||||
target_formdef.name = 'target form'
|
||||
target_formdef.workflow_roles = {'_receiver': 1}
|
||||
target_formdef.backoffice_submission_roles = admin.roles[:]
|
||||
target_formdef.fields = [
|
||||
fields.StringField(id='0', label='string', varname='foo_string'),
|
||||
fields.FileField(id='1', label='file', varname='foo_file'),
|
||||
]
|
||||
target_formdef.store()
|
||||
wf = Workflow(name='create-formdata')
|
||||
|
||||
st1 = wf.add_status('New')
|
||||
st2 = wf.add_status('Resubmit')
|
||||
|
||||
jump = st1.add_action('choice', id='_resubmit')
|
||||
jump.label = 'Resubmit'
|
||||
jump.by = ['_receiver']
|
||||
jump.status = st2.id
|
||||
|
||||
create_formdata = st2.add_action('create_formdata', id='_create_formdata')
|
||||
create_formdata.varname = 'resubmitted'
|
||||
create_formdata.draft = True
|
||||
create_formdata.formdef_slug = target_formdef.url_name
|
||||
create_formdata.user_association_mode = 'keep-user'
|
||||
create_formdata.backoffice_submission = True
|
||||
create_formdata.attach_to_history = request.param.get('attach_to_history', False)
|
||||
create_formdata.mappings = [
|
||||
Mapping(field_id='0', expression='=form_var_toto_string'),
|
||||
Mapping(field_id='1', expression='=form_var_toto_file_raw'),
|
||||
]
|
||||
|
||||
redirect = st2.add_action('redirect_to_url', id='_redirect')
|
||||
redirect.url = '{{ form_links_resubmitted.form_backoffice_url }}'
|
||||
|
||||
jump = st2.add_action('jumponsubmit', id='_jump')
|
||||
jump.status = st1.id
|
||||
|
||||
wf.store()
|
||||
source_formdef.workflow_id = wf.id
|
||||
source_formdef.store()
|
||||
source_formdef.data_class().wipe()
|
||||
target_formdef.data_class().wipe()
|
||||
return locals()
|
||||
|
||||
|
||||
def test_backoffice_create_formdata_backoffice_submission(pub, create_formdata):
|
||||
# create submitting user
|
||||
user = create_formdata['pub'].user_class()
|
||||
user.name = 'Jean Darmette'
|
||||
user.email = 'jean.darmette@triffouilis.fr'
|
||||
user.store()
|
||||
|
||||
# create source formdata
|
||||
formdata = create_formdata['source_formdef'].data_class()()
|
||||
upload = PicklableUpload('/foo/bar', content_type='text/plain')
|
||||
upload.receive([b'hello world'])
|
||||
formdata.data = {
|
||||
'0': 'coucou',
|
||||
'1': upload,
|
||||
}
|
||||
formdata.user = user
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
|
||||
# agent login and go to backoffice management pages
|
||||
app = get_app(create_formdata['pub'])
|
||||
app = login(app)
|
||||
resp = app.get(create_formdata['source_formdef'].get_url(backoffice=True))
|
||||
|
||||
# click on first available formdata
|
||||
resp = resp.click('%s-%s' % (create_formdata['source_formdef'].id, formdata.id))
|
||||
target_data_class = create_formdata['target_formdef'].data_class()
|
||||
assert target_data_class.count() == 0
|
||||
# resubmit it through backoffice submission
|
||||
resp = resp.form.submit(name='button_resubmit')
|
||||
assert pub.loggederror_class.count() == 0
|
||||
assert target_data_class.count() == 1
|
||||
target_formdata = target_data_class.select()[0]
|
||||
|
||||
assert target_formdata.submission_context == {
|
||||
'orig_object_type': 'formdef',
|
||||
'orig_formdata_id': '1',
|
||||
'orig_formdef_id': '1',
|
||||
}
|
||||
assert target_formdata.submission_agent_id == str(create_formdata['admin'].id)
|
||||
assert target_formdata.user.id == user.id
|
||||
assert target_formdata.status == 'draft'
|
||||
assert target_formdata.receipt_time
|
||||
assert resp.location == 'http://example.net/backoffice/management/target-form/%s/' % target_formdata.id
|
||||
resp = resp.follow()
|
||||
assert resp.location == 'http://example.net/backoffice/submission/target-form/%s/' % target_formdata.id
|
||||
resp = resp.follow()
|
||||
# second redirect with magic-token
|
||||
resp = resp.follow()
|
||||
resp = resp.form.submit(name='submit') # -> validation
|
||||
resp = resp.form.submit(name='submit') # -> submission
|
||||
target_formdata = target_data_class.get(id=target_formdata.id)
|
||||
assert target_formdata.user.id == user.id
|
||||
assert target_formdata.status == 'wf-new'
|
||||
resp = resp.follow()
|
||||
pq = resp.pyquery.remove_namespaces()
|
||||
assert pq('.field-type-string .value').text() == 'coucou'
|
||||
assert pq('.field-type-file .value').text() == 'bar'
|
||||
|
||||
|
||||
def test_linked_forms_variables(pub, create_formdata):
|
||||
# create source formdata
|
||||
formdata = create_formdata['source_formdef'].data_class()()
|
||||
upload = PicklableUpload('/foo/bar', content_type='text/plain')
|
||||
upload.receive([b'hello world'])
|
||||
formdata.data = {
|
||||
'0': 'coucou',
|
||||
'1': upload,
|
||||
}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
formdata.store()
|
||||
formdata.jump_status('2')
|
||||
formdata.perform_workflow()
|
||||
formdata.store()
|
||||
|
||||
pub.substitutions.reset()
|
||||
pub.substitutions.feed(formdata)
|
||||
substvars = pub.substitutions.get_context_variables(mode='lazy')
|
||||
assert str(substvars['form_links_resubmitted_form_var_foo_string']) == 'coucou'
|
||||
assert 'form_links_resubmitted_form_var_foo_string' in substvars.get_flat_keys()
|
||||
|
||||
source_formdata = create_formdata['source_formdef'].data_class().select()[0]
|
||||
|
||||
app = get_app(create_formdata['pub'])
|
||||
app = login(app)
|
||||
resp = app.get(source_formdata.get_url(backoffice=True) + 'inspect')
|
||||
assert '?expand=form_links_resubmitted' in resp
|
||||
resp = app.get(source_formdata.get_url(backoffice=True) + 'inspect?expand=form_links_resubmitted')
|
||||
assert 'form_links_resubmitted_form_var_foo_string' in resp
|
||||
|
||||
# delete target formdata
|
||||
create_formdata['target_formdef'].data_class().wipe()
|
||||
resp = app.get(source_formdata.get_url(backoffice=True) + 'inspect')
|
||||
assert '?expand=form_links_resubmitted' not in resp
|
||||
assert 'form_links_resubmitted_form_var_foo_string' not in resp
|
||||
|
||||
# delete target formdef
|
||||
create_formdata['target_formdef'].remove_self()
|
||||
resp = app.get(source_formdata.get_url(backoffice=True) + 'inspect')
|
||||
|
||||
|
||||
def test_backoffice_create_formdata_map_fields_by_varname(pub, create_formdata):
|
||||
create_formdata['create_formdata'].map_fields_by_varname = True
|
||||
create_formdata['create_formdata'].mappings = []
|
||||
create_formdata['wf'].store()
|
||||
create_formdata['source_formdef'].fields = [
|
||||
fields.StringField(id='0', label='string', varname='string0'),
|
||||
fields.FileField(id='1', label='file', varname='file1'),
|
||||
fields.StringField(id='2', label='string', varname='string2', required=False),
|
||||
fields.FileField(id='3', label='file', varname='file3', required=False),
|
||||
]
|
||||
create_formdata['source_formdef'].store()
|
||||
create_formdata['target_formdef'].fields = [
|
||||
fields.StringField(id='0', label='string', varname='string0'),
|
||||
fields.FileField(id='1', label='file', varname='file1'),
|
||||
fields.StringField(id='2', label='string', varname='string2', required=False),
|
||||
fields.FileField(id='3', label='file', varname='file3', required=False),
|
||||
]
|
||||
create_formdata['target_formdef'].store()
|
||||
|
||||
# create submitting user
|
||||
user = create_formdata['pub'].user_class()
|
||||
user.name = 'Jean Darmette'
|
||||
user.email = 'jean.darmette@triffouilis.fr'
|
||||
user.store()
|
||||
|
||||
# create source formdata
|
||||
create_formdata['source_formdef'].digest_templates = {'default': 'blah'}
|
||||
create_formdata['source_formdef'].store()
|
||||
formdata = create_formdata['source_formdef'].data_class()()
|
||||
create_formdata['formdata'] = formdata
|
||||
upload = PicklableUpload('/foo/bar', content_type='text/plain')
|
||||
upload.receive([b'hello world'])
|
||||
formdata.data = {
|
||||
'0': 'coucou',
|
||||
'1': upload,
|
||||
}
|
||||
formdata.user = user
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
|
||||
# agent login and go to backoffice management pages
|
||||
app = get_app(create_formdata['pub'])
|
||||
app = login(app)
|
||||
resp = app.get(create_formdata['source_formdef'].get_url(backoffice=True))
|
||||
|
||||
# click on first available formdata
|
||||
resp = resp.click('%s-%s' % (create_formdata['source_formdef'].id, formdata.id))
|
||||
target_data_class = create_formdata['target_formdef'].data_class()
|
||||
assert target_data_class.count() == 0
|
||||
# resubmit it through backoffice submission
|
||||
resp = resp.form.submit(name='button_resubmit')
|
||||
assert pub.loggederror_class.count() == 0
|
||||
assert target_data_class.count() == 1
|
||||
target_formdata = target_data_class.select()[0]
|
||||
|
||||
assert target_formdata.submission_context == {
|
||||
'orig_object_type': 'formdef',
|
||||
'orig_formdata_id': '1',
|
||||
'orig_formdef_id': '1',
|
||||
}
|
||||
assert target_formdata.submission_agent_id == str(create_formdata['admin'].id)
|
||||
assert target_formdata.user.id == user.id
|
||||
assert target_formdata.status == 'draft'
|
||||
assert resp.location == 'http://example.net/backoffice/management/target-form/%s/' % target_formdata.id
|
||||
resp = resp.follow()
|
||||
assert resp.location == 'http://example.net/backoffice/submission/target-form/%s/' % target_formdata.id
|
||||
resp = resp.follow()
|
||||
# second redirect with magic-token
|
||||
resp = resp.follow()
|
||||
# check parent form is displayed in sidebar
|
||||
assert resp.pyquery('.extra-context--orig-data').attr.href == formdata.get_backoffice_url()
|
||||
assert resp.pyquery('.extra-context--orig-data').text() == 'source form #1-1 (blah)'
|
||||
resp = resp.form.submit(name='submit') # -> validation
|
||||
resp = resp.form.submit(name='submit') # -> submission
|
||||
target_formdata = target_data_class.get(id=target_formdata.id)
|
||||
assert target_formdata.user.id == user.id
|
||||
assert target_formdata.status == 'wf-new'
|
||||
resp = resp.follow()
|
||||
pq = resp.pyquery.remove_namespaces()
|
||||
assert pq('.field-type-string .value').text() == 'coucou'
|
||||
assert pq('.field-type-file .value').text() == 'bar'
|
||||
|
||||
resp = app.get(create_formdata['formdata'].get_url(backoffice=True))
|
||||
pq = resp.pyquery.remove_namespaces()
|
||||
assert pq('.field-type-string .value').text() == 'coucou'
|
||||
if create_formdata['create_formdata'].attach_to_history:
|
||||
assert pq('.wf-links')
|
||||
else:
|
||||
assert not pq('.wf-links')
|
||||
|
||||
|
||||
def test_backoffice_create_formdata_map_fields_by_varname_plus_empty(pub, create_formdata):
|
||||
create_formdata['create_formdata'].map_fields_by_varname = True
|
||||
create_formdata['create_formdata'].mappings = [
|
||||
Mapping(field_id='0', expression=None),
|
||||
]
|
||||
create_formdata['wf'].store()
|
||||
create_formdata['source_formdef'].fields = [
|
||||
fields.StringField(id='0', label='string', varname='string0'),
|
||||
fields.StringField(id='2', label='string', varname='string2', required=False),
|
||||
]
|
||||
create_formdata['source_formdef'].store()
|
||||
create_formdata['target_formdef'].fields = [
|
||||
fields.StringField(id='0', label='string', varname='string0'),
|
||||
fields.StringField(id='2', label='string', varname='string2', required=False),
|
||||
]
|
||||
create_formdata['target_formdef'].store()
|
||||
|
||||
# create submitting user
|
||||
user = create_formdata['pub'].user_class()
|
||||
user.name = 'Jean Darmette'
|
||||
user.email = 'jean.darmette@triffouilis.fr'
|
||||
user.store()
|
||||
|
||||
# create source formdata
|
||||
formdata = create_formdata['source_formdef'].data_class()()
|
||||
create_formdata['formdata'] = formdata
|
||||
formdata.data = {
|
||||
'0': 'foo',
|
||||
'2': 'bar',
|
||||
}
|
||||
formdata.user = user
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
|
||||
# agent login and go to backoffice management pages
|
||||
app = get_app(create_formdata['pub'])
|
||||
app = login(app)
|
||||
resp = app.get(create_formdata['source_formdef'].get_url(backoffice=True))
|
||||
|
||||
# click on first available formdata
|
||||
resp = resp.click('%s-%s' % (create_formdata['source_formdef'].id, formdata.id))
|
||||
target_data_class = create_formdata['target_formdef'].data_class()
|
||||
assert target_data_class.count() == 0
|
||||
# resubmit it through backoffice submission
|
||||
resp = resp.form.submit(name='button_resubmit')
|
||||
assert target_data_class.count() == 1
|
||||
target_formdata = target_data_class.select()[0]
|
||||
|
||||
assert target_formdata.submission_context == {
|
||||
'orig_object_type': 'formdef',
|
||||
'orig_formdata_id': '1',
|
||||
'orig_formdef_id': '1',
|
||||
}
|
||||
assert target_formdata.submission_agent_id == str(create_formdata['admin'].id)
|
||||
assert target_formdata.user.id == user.id
|
||||
assert target_formdata.status == 'draft'
|
||||
assert target_formdata.data == {'0': None, '2': 'bar'}
|
||||
|
||||
|
||||
def test_backoffice_create_carddata_from_formdata(pub):
|
||||
CardDef.wipe()
|
||||
FormDef.wipe()
|
||||
|
@ -6146,6 +6020,7 @@ def test_anonymise_action_intermediate(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'0': 'Foo', '1': 'Bar'}
|
||||
formdata.user_id = user.id
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
|
@ -6153,6 +6028,7 @@ def test_anonymise_action_intermediate(pub):
|
|||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'wf-anonymise_intermediate'
|
||||
assert formdata.data == {'0': None, '1': 'Bar'}
|
||||
assert formdata.user_id
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/management/foo/%s/' % formdata.id)
|
||||
|
@ -6165,6 +6041,7 @@ def test_anonymise_action_intermediate(pub):
|
|||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'wf-anonymise_final'
|
||||
assert formdata.data == {'0': None, '1': None}
|
||||
assert not formdata.user_id
|
||||
|
||||
|
||||
def test_anonymise_action_final_also_deletes_fields_with_intermediate(pub):
|
||||
|
@ -6285,3 +6162,33 @@ def test_status_visibility(pub):
|
|||
wf.store()
|
||||
resp = app.get(formdata.get_backoffice_url())
|
||||
assert resp.pyquery('.status').text() == 'st1 st2 st3'
|
||||
|
||||
|
||||
def test_backoffice_form_tracking_code_workflow_action(pub):
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test'
|
||||
formdef.fields = [fields.StringField(id='0', label='string')]
|
||||
formdef.enable_tracking_codes = True
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
# as user
|
||||
resp = get_app(pub).get('/test/')
|
||||
resp.forms[0]['f0'] = 'foobar'
|
||||
resp = resp.forms[0].submit('submit') # -> validation
|
||||
resp = resp.forms[0].submit('submit') # -> done
|
||||
formdata = formdef.data_class().select()[0]
|
||||
|
||||
user = create_user(pub)
|
||||
resp = login(get_app(pub)).get('/backoffice/management/').follow()
|
||||
resp.forms[0]['query'] = formdata.tracking_code
|
||||
resp = resp.forms[0].submit()
|
||||
resp = resp.follow()
|
||||
resp.forms['wf-actions']['comment'] = 'Test comment'
|
||||
resp = resp.forms['wf-actions'].submit('button_commentable')
|
||||
|
||||
# check action has been recorded as agent
|
||||
formdata.refresh_from_storage()
|
||||
assert isinstance(formdata.evolution[-1].parts[0], WorkflowCommentPart)
|
||||
assert formdata.evolution[-1].who == str(user.id)
|
||||
|
|
|
@ -130,8 +130,10 @@ def test_formdefs(pub, application_with_icon, application_without_icon, icon):
|
|||
)
|
||||
else:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img')) == 0
|
||||
assert 'Forms outside applications' in resp
|
||||
|
||||
# check application view
|
||||
resp = app.get('/backoffice/forms/')
|
||||
resp = resp.click(href='application/%s/' % application.slug)
|
||||
assert resp.pyquery('h2').text() == application.name
|
||||
if icon:
|
||||
|
@ -145,6 +147,13 @@ def test_formdefs(pub, application_with_icon, application_without_icon, icon):
|
|||
assert resp.pyquery('.section ul.objects-list li:nth-child(2)').text() == 'form3'
|
||||
assert len(resp.pyquery('.section ul.objects-list img')) == 0
|
||||
|
||||
# check elements outside applications
|
||||
resp = resp.click(href='application/')
|
||||
assert resp.pyquery('h2').text() == 'Forms outside applications'
|
||||
assert len(resp.pyquery('.section')) == 1
|
||||
assert len(resp.pyquery('.section ul.objects-list li')) == 1
|
||||
assert resp.pyquery('.section ul.objects-list li:nth-child(1)').text() == 'form1'
|
||||
|
||||
# with category
|
||||
cat = Category()
|
||||
cat.name = 'cat'
|
||||
|
@ -192,13 +201,21 @@ def test_formdefs(pub, application_with_icon, application_without_icon, icon):
|
|||
cat2 = Category()
|
||||
cat2.name = 'cat2'
|
||||
cat2.store()
|
||||
cat3 = Category()
|
||||
cat3.name = 'cat3'
|
||||
cat3.store()
|
||||
resp = app.get('/backoffice/forms/categories/')
|
||||
assert resp.pyquery('h3:contains("Applications") + .button-paragraph').text() == application.name
|
||||
assert len(resp.pyquery('ul.biglist li')) == 2
|
||||
assert len(resp.pyquery('ul.biglist li')) == 3
|
||||
resp = resp.click(href='application/%s/' % application.slug)
|
||||
assert resp.pyquery('h2').text() == application.name
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 1
|
||||
|
||||
# check categories outside applications
|
||||
resp = resp.click(href='application/')
|
||||
assert resp.pyquery('h2').text() == 'Categories outside applications'
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 2
|
||||
|
||||
# check detail page
|
||||
resp = app.get('/backoffice/forms/%s/' % formdef1.id)
|
||||
assert len(resp.pyquery('h3:contains("Applications")')) == 0
|
||||
|
@ -296,6 +313,7 @@ def test_carddefs(pub, application_with_icon, application_without_icon, icon):
|
|||
)
|
||||
else:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img')) == 0
|
||||
assert 'Card models outside applications' in resp
|
||||
|
||||
# check application view
|
||||
resp = resp.click(href='application/%s/' % application.slug)
|
||||
|
@ -311,6 +329,13 @@ def test_carddefs(pub, application_with_icon, application_without_icon, icon):
|
|||
assert resp.pyquery('.section ul.objects-list li:nth-child(2)').text() == 'card3'
|
||||
assert len(resp.pyquery('.section ul.objects-list img')) == 0
|
||||
|
||||
# check elements outside applications
|
||||
resp = resp.click(href='application/')
|
||||
assert resp.pyquery('h2').text() == 'Card models outside applications'
|
||||
assert len(resp.pyquery('.section')) == 1
|
||||
assert len(resp.pyquery('.section ul.objects-list li')) == 1
|
||||
assert resp.pyquery('.section ul.objects-list li:nth-child(1)').text() == 'card1'
|
||||
|
||||
# with category
|
||||
cat = CardDefCategory()
|
||||
cat.name = 'cat'
|
||||
|
@ -380,13 +405,21 @@ def test_carddefs(pub, application_with_icon, application_without_icon, icon):
|
|||
cat2 = CardDefCategory()
|
||||
cat2.name = 'cat2'
|
||||
cat2.store()
|
||||
cat3 = CardDefCategory()
|
||||
cat3.name = 'cat3'
|
||||
cat3.store()
|
||||
resp = app.get('/backoffice/cards/categories/')
|
||||
assert resp.pyquery('h3:contains("Applications") + .button-paragraph').text() == application.name
|
||||
assert len(resp.pyquery('ul.biglist li')) == 2
|
||||
assert len(resp.pyquery('ul.biglist li')) == 3
|
||||
resp = resp.click(href='application/%s/' % application.slug)
|
||||
assert resp.pyquery('h2').text() == application.name
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 1
|
||||
|
||||
# check categories outside applications
|
||||
resp = resp.click(href='application/')
|
||||
assert resp.pyquery('h2').text() == 'Categories outside applications'
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 2
|
||||
|
||||
# check visible flag
|
||||
application = Application.get(application.id)
|
||||
application.visible = False
|
||||
|
@ -496,6 +529,7 @@ def test_workflows(pub, application_with_icon, application_without_icon, icon):
|
|||
)
|
||||
else:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img')) == 0
|
||||
assert 'Workflows outside applications' in resp
|
||||
|
||||
# check application view
|
||||
resp = resp.click(href='application/%s/' % application.slug)
|
||||
|
@ -510,6 +544,13 @@ def test_workflows(pub, application_with_icon, application_without_icon, icon):
|
|||
assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'workflow2 Forms and card models'
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(2)').text() == 'workflow3 Unused'
|
||||
|
||||
# check elements outside applications
|
||||
resp = resp.click(href='application/')
|
||||
assert resp.pyquery('h2').text() == 'Workflows outside applications'
|
||||
assert len(resp.pyquery('.section')) == 1
|
||||
assert len(resp.pyquery('.section ul.objects-list li')) == 1
|
||||
assert resp.pyquery('.section ul.objects-list li:nth-child(1)').text() == 'workflow1 Unused'
|
||||
|
||||
# with category
|
||||
cat = WorkflowCategory()
|
||||
cat.name = 'cat'
|
||||
|
@ -604,13 +645,21 @@ def test_workflows(pub, application_with_icon, application_without_icon, icon):
|
|||
cat2 = WorkflowCategory()
|
||||
cat2.name = 'cat2'
|
||||
cat2.store()
|
||||
cat3 = WorkflowCategory()
|
||||
cat3.name = 'cat3'
|
||||
cat3.store()
|
||||
resp = app.get('/backoffice/workflows/categories/')
|
||||
assert resp.pyquery('h3:contains("Applications") + .button-paragraph').text() == application.name
|
||||
assert len(resp.pyquery('ul.biglist li')) == 2
|
||||
assert len(resp.pyquery('ul.biglist li')) == 3
|
||||
resp = resp.click(href='application/%s/' % application.slug)
|
||||
assert resp.pyquery('h2').text() == application.name
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 1
|
||||
|
||||
# check categories outside applications
|
||||
resp = resp.click(href='application/')
|
||||
assert resp.pyquery('h2').text() == 'Categories outside applications'
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 2
|
||||
|
||||
# check visible flag
|
||||
application = Application.get(application.id)
|
||||
application.visible = False
|
||||
|
@ -685,6 +734,7 @@ def test_blockdefs(pub, application_with_icon, application_without_icon, icon):
|
|||
)
|
||||
else:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img')) == 0
|
||||
assert 'Field blocks outside applications' in resp
|
||||
|
||||
# check application view
|
||||
resp = resp.click(href='application/%s/' % application.slug)
|
||||
|
@ -699,6 +749,12 @@ def test_blockdefs(pub, application_with_icon, application_without_icon, icon):
|
|||
assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'block2'
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(2)').text() == 'block3'
|
||||
|
||||
# check elements outside applications
|
||||
resp = resp.click(href='application/')
|
||||
assert resp.pyquery('h2').text() == 'Field blocks outside applications'
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 1
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'block1'
|
||||
|
||||
# with category
|
||||
cat = BlockCategory()
|
||||
cat.name = 'cat'
|
||||
|
@ -768,13 +824,21 @@ def test_blockdefs(pub, application_with_icon, application_without_icon, icon):
|
|||
cat2 = BlockCategory()
|
||||
cat2.name = 'cat2'
|
||||
cat2.store()
|
||||
cat3 = BlockCategory()
|
||||
cat3.name = 'cat3'
|
||||
cat3.store()
|
||||
resp = app.get('/backoffice/forms/blocks/categories/')
|
||||
assert resp.pyquery('h3:contains("Applications") + .button-paragraph').text() == application.name
|
||||
assert len(resp.pyquery('ul.biglist li')) == 2
|
||||
assert len(resp.pyquery('ul.biglist li')) == 3
|
||||
resp = resp.click(href='application/%s/' % application.slug)
|
||||
assert resp.pyquery('h2').text() == application.name
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 1
|
||||
|
||||
# check categories outside applications
|
||||
resp = resp.click(href='application/')
|
||||
assert resp.pyquery('h2').text() == 'Categories outside applications'
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 2
|
||||
|
||||
# check visible flag
|
||||
application = Application.get(application.id)
|
||||
application.visible = False
|
||||
|
@ -849,6 +913,7 @@ def test_mailtemplates(pub, application_with_icon, application_without_icon, ico
|
|||
)
|
||||
else:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img')) == 0
|
||||
assert 'Mail templates outside applications' in resp
|
||||
|
||||
# check application view
|
||||
resp = resp.click(href='application/%s/' % application.slug)
|
||||
|
@ -863,6 +928,12 @@ def test_mailtemplates(pub, application_with_icon, application_without_icon, ico
|
|||
assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'mailtemplate2'
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(2)').text() == 'mailtemplate3'
|
||||
|
||||
# check elements outside applications
|
||||
resp = resp.click(href='application/')
|
||||
assert resp.pyquery('h2').text() == 'Mail templates outside applications'
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 1
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'mailtemplate1'
|
||||
|
||||
# with category
|
||||
cat = MailTemplateCategory()
|
||||
cat.name = 'cat'
|
||||
|
@ -942,13 +1013,21 @@ def test_mailtemplates(pub, application_with_icon, application_without_icon, ico
|
|||
cat2 = MailTemplateCategory()
|
||||
cat2.name = 'cat2'
|
||||
cat2.store()
|
||||
cat3 = MailTemplateCategory()
|
||||
cat3.name = 'cat3'
|
||||
cat3.store()
|
||||
resp = app.get('/backoffice/workflows/mail-templates/categories/')
|
||||
assert resp.pyquery('h3:contains("Applications") + .button-paragraph').text() == application.name
|
||||
assert len(resp.pyquery('ul.biglist li')) == 2
|
||||
assert len(resp.pyquery('ul.biglist li')) == 3
|
||||
resp = resp.click(href='application/%s/' % application.slug)
|
||||
assert resp.pyquery('h2').text() == application.name
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 1
|
||||
|
||||
# check categories outside applications
|
||||
resp = resp.click(href='application/')
|
||||
assert resp.pyquery('h2').text() == 'Categories outside applications'
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 2
|
||||
|
||||
# check visible flag
|
||||
application = Application.get(application.id)
|
||||
application.visible = False
|
||||
|
@ -1023,6 +1102,7 @@ def test_commenttemplates(pub, application_with_icon, application_without_icon,
|
|||
)
|
||||
else:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img')) == 0
|
||||
assert 'Comment templates outside applications' in resp
|
||||
|
||||
# check application view
|
||||
resp = resp.click(href='application/%s/' % application.slug)
|
||||
|
@ -1037,6 +1117,12 @@ def test_commenttemplates(pub, application_with_icon, application_without_icon,
|
|||
assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'commenttemplate2'
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(2)').text() == 'commenttemplate3'
|
||||
|
||||
# check elements outside applications
|
||||
resp = resp.click(href='application/')
|
||||
assert resp.pyquery('h2').text() == 'Comment templates outside applications'
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 1
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'commenttemplate1'
|
||||
|
||||
# with category
|
||||
cat = CommentTemplateCategory()
|
||||
cat.name = 'cat'
|
||||
|
@ -1121,13 +1207,21 @@ def test_commenttemplates(pub, application_with_icon, application_without_icon,
|
|||
cat2 = CommentTemplateCategory()
|
||||
cat2.name = 'cat2'
|
||||
cat2.store()
|
||||
cat3 = CommentTemplateCategory()
|
||||
cat3.name = 'cat3'
|
||||
cat3.store()
|
||||
resp = app.get('/backoffice/workflows/comment-templates/categories/')
|
||||
assert resp.pyquery('h3:contains("Applications") + .button-paragraph').text() == application.name
|
||||
assert len(resp.pyquery('ul.biglist li')) == 2
|
||||
assert len(resp.pyquery('ul.biglist li')) == 3
|
||||
resp = resp.click(href='application/%s/' % application.slug)
|
||||
assert resp.pyquery('h2').text() == application.name
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 1
|
||||
|
||||
# check categories outside applications
|
||||
resp = resp.click(href='application/')
|
||||
assert resp.pyquery('h2').text() == 'Categories outside applications'
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 2
|
||||
|
||||
# check visible flag
|
||||
application = Application.get(application.id)
|
||||
application.visible = False
|
||||
|
@ -1273,6 +1367,7 @@ def test_datasources(pub, application_with_icon, application_without_icon, icon)
|
|||
)
|
||||
else:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img')) == 0
|
||||
assert 'Data sources outside applications' in resp
|
||||
|
||||
# check application view
|
||||
resp = resp.click(href='application/%s/' % application.slug)
|
||||
|
@ -1306,6 +1401,29 @@ def test_datasources(pub, application_with_icon, application_without_icon, icon)
|
|||
assert len(PyQuery(resp.pyquery('.section')[2]).find('ul.objects-list li')) == 1
|
||||
assert PyQuery(resp.pyquery('.section')[2]).find('ul.objects-list li:nth-child(1)').text() == 'card2'
|
||||
|
||||
# check elements outside applications
|
||||
resp = resp.click(href='application/')
|
||||
assert resp.pyquery('#appbar h2').text() == 'Data sources outside applications'
|
||||
assert len(resp.pyquery('.section')) == 3
|
||||
assert PyQuery(resp.pyquery('.section')[0]).find('h2').text() == 'Users Data Sources'
|
||||
assert len(PyQuery(resp.pyquery('.section')[0]).find('ul.objects-list li')) == 1
|
||||
assert (
|
||||
PyQuery(resp.pyquery('.section')[0]).find('ul.objects-list li:nth-child(1)').text()
|
||||
== 'user datasource1 (user_datasource1)'
|
||||
)
|
||||
assert PyQuery(resp.pyquery('.section')[1]).find('h2').text() == 'Manually Configured Data Sources'
|
||||
assert len(PyQuery(resp.pyquery('.section')[1]).find('ul.objects-list li')) == 1
|
||||
assert (
|
||||
PyQuery(resp.pyquery('.section')[1]).find('ul.objects-list li:nth-child(1)').text()
|
||||
== 'datasource1 (datasource1)'
|
||||
)
|
||||
assert (
|
||||
PyQuery(resp.pyquery('.section')[2]).find('h2').text()
|
||||
== 'Data Sources from Card Models - automatically configured'
|
||||
)
|
||||
assert len(PyQuery(resp.pyquery('.section')[2]).find('ul.objects-list li')) == 1
|
||||
assert PyQuery(resp.pyquery('.section')[2]).find('ul.objects-list li:nth-child(1)').text() == 'card1'
|
||||
|
||||
# with category
|
||||
cat = DataSourceCategory()
|
||||
cat.name = 'cat'
|
||||
|
@ -1414,13 +1532,21 @@ def test_datasources(pub, application_with_icon, application_without_icon, icon)
|
|||
cat2 = DataSourceCategory()
|
||||
cat2.name = 'cat2'
|
||||
cat2.store()
|
||||
cat3 = DataSourceCategory()
|
||||
cat3.name = 'cat3'
|
||||
cat3.store()
|
||||
resp = app.get('/backoffice/forms/data-sources/categories/')
|
||||
assert resp.pyquery('h3:contains("Applications") + .button-paragraph').text() == application.name
|
||||
assert len(resp.pyquery('ul.biglist li')) == 2
|
||||
assert len(resp.pyquery('ul.biglist li')) == 3
|
||||
resp = resp.click(href='application/%s/' % application.slug)
|
||||
assert resp.pyquery('h2').text() == application.name
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 1
|
||||
|
||||
# check categories outside applications
|
||||
resp = resp.click(href='application/')
|
||||
assert resp.pyquery('h2').text() == 'Categories outside applications'
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 2
|
||||
|
||||
# check visible flag
|
||||
application = Application.get(application.id)
|
||||
application.visible = False
|
||||
|
@ -1492,6 +1618,7 @@ def test_wscalls(pub, application_with_icon, application_without_icon, icon):
|
|||
)
|
||||
else:
|
||||
assert len(resp.pyquery('h3:contains("Applications") + .button-paragraph img')) == 0
|
||||
assert 'Webservice calls outside applications' in resp
|
||||
|
||||
# check application view
|
||||
resp = resp.click(href='application/%s/' % application.slug)
|
||||
|
@ -1505,6 +1632,12 @@ def test_wscalls(pub, application_with_icon, application_without_icon, icon):
|
|||
assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'wscall2 (wscall2)'
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(2)').text() == 'wscall3 (wscall3)'
|
||||
|
||||
# check elements outside applications
|
||||
resp = resp.click(href='application/')
|
||||
assert resp.pyquery('h2').text() == 'Webservice calls outside applications'
|
||||
assert len(resp.pyquery('ul.objects-list li')) == 1
|
||||
assert resp.pyquery('ul.objects-list li:nth-child(1)').text() == 'wscall1 (wscall1)'
|
||||
|
||||
# check detail page
|
||||
resp = app.get('/backoffice/settings/wscalls/%s/' % wscall1.id)
|
||||
assert len(resp.pyquery('h3:contains("Applications")')) == 0
|
||||
|
|
|
@ -368,6 +368,41 @@ def test_studio_card_item_link(pub):
|
|||
resp.click('card plop')
|
||||
|
||||
|
||||
def test_backoffice_card_item_link_id_template(pub):
|
||||
user = create_user(pub)
|
||||
CardDef.wipe()
|
||||
carddef = CardDef()
|
||||
carddef.name = 'foo'
|
||||
carddef.fields = [
|
||||
fields.StringField(id='1', label='Test', varname='foo'),
|
||||
fields.StringField(id='2', label='Custom id', varname='custom_id'),
|
||||
]
|
||||
carddef.backoffice_submission_roles = user.roles
|
||||
carddef.workflow_roles = {'_editor': user.roles[0]}
|
||||
carddef.id_template = '{{form_var_custom_id}}'
|
||||
carddef.digest_templates = {'default': 'card {{ form_var_foo }}'}
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
|
||||
card = carddef.data_class()()
|
||||
card.data = {'1': 'plop', '2': 'test'}
|
||||
card.just_created()
|
||||
card.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/data/foo/')
|
||||
assert [x.attrib['href'] for x in resp.pyquery('table a')] == ['test/']
|
||||
resp = resp.click('Add')
|
||||
resp.form['f1'] = 'blah'
|
||||
resp.form['f2'] = 'blah'
|
||||
resp = resp.form.submit('submit')
|
||||
assert resp.location.endswith('/backoffice/data/foo/blah/')
|
||||
resp = resp.follow()
|
||||
assert resp.pyquery('.breadcrumbs a')[-1].attrib['href'] == '/backoffice/data/foo/blah/'
|
||||
resp = app.get('/backoffice/data/foo/')
|
||||
assert [x.attrib['href'] for x in resp.pyquery('table a')] == ['blah/', 'test/']
|
||||
|
||||
|
||||
def test_backoffice_cards_import_data_from_csv(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
|
@ -647,6 +682,110 @@ def test_backoffice_cards_import_data_csv_no_backoffice_fields(pub):
|
|||
assert carddef.data_class().count() == 2
|
||||
|
||||
|
||||
def test_backoffice_cards_import_data_csv_custom_id_no_update(pub):
|
||||
user = create_user(pub)
|
||||
user.name_identifiers = [str(uuid.uuid4())]
|
||||
user.store()
|
||||
|
||||
Workflow.wipe()
|
||||
workflow = Workflow(name='form-title')
|
||||
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
|
||||
workflow.backoffice_fields_formdef.fields = [
|
||||
fields.StringField(id='bo0', varname='foo_bovar', label='bo variable'),
|
||||
]
|
||||
workflow.add_status('st0')
|
||||
workflow.store()
|
||||
|
||||
CardDef.wipe()
|
||||
carddef = CardDef()
|
||||
carddef.name = 'test'
|
||||
carddef.fields = [
|
||||
fields.StringField(id='1', label='String', varname='custom_id'),
|
||||
fields.ItemField(id='2', label='List', items=['item1', 'item2']),
|
||||
]
|
||||
carddef.backoffice_submission_roles = user.roles
|
||||
carddef.id_template = '{{form_var_custom_id}}'
|
||||
carddef.workflow = workflow
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
|
||||
card = carddef.data_class()()
|
||||
card.data = {'1': 'plop', '2': 'test', '2_display': 'test', 'bo0': 'xxx'}
|
||||
card.just_created()
|
||||
card.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
data = b'''\
|
||||
"String","List"
|
||||
"plop","item1"
|
||||
"test","item2"
|
||||
'''
|
||||
resp = app.get('/backoffice/data/test/import-file')
|
||||
resp.forms[0]['file'] = Upload('test.csv', data, 'text/csv')
|
||||
resp.form['update_existing_cards'].checked = False
|
||||
resp = resp.forms[0].submit().follow()
|
||||
assert carddef.data_class().count() == 2
|
||||
|
||||
card.refresh_from_storage()
|
||||
assert card.data == {'1': 'plop', '2': 'test', '2_display': 'test', 'bo0': 'xxx'} # no change
|
||||
|
||||
other_card = carddef.data_class().select(order_by='-receipt_time')[0]
|
||||
assert other_card.data == {'1': 'test', '2': 'item2', '2_display': 'item2', 'bo0': None}
|
||||
assert other_card.id_display == 'test'
|
||||
|
||||
|
||||
def test_backoffice_cards_import_data_csv_custom_id_update(pub):
|
||||
user = create_user(pub)
|
||||
user.name_identifiers = [str(uuid.uuid4())]
|
||||
user.store()
|
||||
|
||||
Workflow.wipe()
|
||||
workflow = Workflow(name='form-title')
|
||||
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
|
||||
workflow.backoffice_fields_formdef.fields = [
|
||||
fields.StringField(id='bo0', varname='foo_bovar', label='bo variable'),
|
||||
]
|
||||
workflow.add_status('st0')
|
||||
workflow.store()
|
||||
|
||||
CardDef.wipe()
|
||||
carddef = CardDef()
|
||||
carddef.name = 'test'
|
||||
carddef.fields = [
|
||||
fields.StringField(id='1', label='String', varname='custom_id'),
|
||||
fields.ItemField(id='2', label='List', items=['item1', 'item2']),
|
||||
]
|
||||
carddef.backoffice_submission_roles = user.roles
|
||||
carddef.id_template = '{{form_var_custom_id}}'
|
||||
carddef.workflow = workflow
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
|
||||
card = carddef.data_class()()
|
||||
card.data = {'1': 'plop', '2': 'test', 'bo0': 'xxx'}
|
||||
card.just_created()
|
||||
card.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
data = b'''\
|
||||
"String","List"
|
||||
"plop","item1"
|
||||
"test","item2"
|
||||
'''
|
||||
resp = app.get('/backoffice/data/test/import-file')
|
||||
resp.forms[0]['file'] = Upload('test.csv', data, 'text/csv')
|
||||
resp.form['update_existing_cards'].checked = True
|
||||
resp = resp.forms[0].submit().follow()
|
||||
assert carddef.data_class().count() == 2
|
||||
|
||||
card.refresh_from_storage()
|
||||
assert card.data == {'1': 'plop', '2': 'item1', '2_display': 'item1', 'bo0': 'xxx'}
|
||||
|
||||
other_card = carddef.data_class().select(order_by='-receipt_time')[0]
|
||||
assert other_card.data == {'1': 'test', '2': 'item2', '2_display': 'item2', 'bo0': None}
|
||||
assert other_card.id_display == 'test'
|
||||
|
||||
|
||||
def test_backoffice_cards_import_data_csv_blockfield(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
|
@ -1070,6 +1209,10 @@ def test_backoffice_cards_update_data_from_json(pub):
|
|||
workflow = CardDef.get_default_workflow()
|
||||
workflow.id = None
|
||||
workflow.add_status('status2', 'st2')
|
||||
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
|
||||
workflow.backoffice_fields_formdef.fields = [
|
||||
fields.StringField(id='bo1', label='bo field 1', varname='bo_data'),
|
||||
]
|
||||
workflow.store()
|
||||
|
||||
CardDef.wipe()
|
||||
|
@ -1099,6 +1242,7 @@ def test_backoffice_cards_update_data_from_json(pub):
|
|||
json_export = json.loads(job.file_content)
|
||||
assert len(json_export['data']) == 1
|
||||
json_export['data'][0]['fields']['string'] = 'plop 2'
|
||||
json_export['data'][0]['workflow']['fields']['bo_data'] = 'plop 2'
|
||||
|
||||
# update
|
||||
resp = app.get(carddef.get_url())
|
||||
|
@ -1111,10 +1255,27 @@ def test_backoffice_cards_update_data_from_json(pub):
|
|||
|
||||
assert carddef.data_class().count() == 1
|
||||
card.refresh_from_storage()
|
||||
assert card.data == {'1': 'plop 2'}
|
||||
assert card.data == {'1': 'plop 2', 'bo1': 'plop 2'}
|
||||
assert isinstance(card.evolution[0].parts[-1], ContentSnapshotPart)
|
||||
assert card.evolution[0].parts[-1].old_data == {'1': 'plop'}
|
||||
assert card.evolution[0].parts[-1].new_data == {'1': 'plop 2'}
|
||||
assert card.evolution[0].parts[-1].old_data == {'1': 'plop', 'bo1': None}
|
||||
assert card.evolution[0].parts[-1].new_data == {'1': 'plop 2', 'bo1': 'plop 2'}
|
||||
|
||||
# update and reset backoffice fields
|
||||
json_export['data'][0]['workflow']['fields']['bo_data'] = None
|
||||
resp = app.get(carddef.get_url())
|
||||
resp = resp.click('Import data from a file')
|
||||
resp.forms[0]['file'] = Upload('test.json', json.dumps(json_export).encode(), 'application/json')
|
||||
resp.form['update_existing_cards'].checked = True
|
||||
resp = resp.forms[0].submit()
|
||||
assert '/backoffice/processing?job=' in resp.location
|
||||
resp = resp.follow()
|
||||
|
||||
assert carddef.data_class().count() == 1
|
||||
card.refresh_from_storage()
|
||||
assert card.data == {'1': 'plop 2', 'bo1': None}
|
||||
assert isinstance(card.evolution[0].parts[-1], ContentSnapshotPart)
|
||||
assert card.evolution[0].parts[-1].old_data == {'1': 'plop 2', 'bo1': 'plop 2'}
|
||||
assert card.evolution[0].parts[-1].new_data == {'1': 'plop 2', 'bo1': None}
|
||||
|
||||
# no uuid -> create
|
||||
json_export['data'][0]['uuid'] = None
|
||||
|
@ -1139,7 +1300,10 @@ def test_backoffice_cards_update_data_from_json(pub):
|
|||
assert '/backoffice/processing?job=' in resp.location
|
||||
resp = resp.follow()
|
||||
assert carddef.data_class().count() == 3
|
||||
assert carddef.data_class().get_by_uuid(json_export['data'][0]['uuid']).data == {'1': 'plop 4'}
|
||||
assert carddef.data_class().get_by_uuid(json_export['data'][0]['uuid']).data == {
|
||||
'1': 'plop 4',
|
||||
'bo1': None,
|
||||
}
|
||||
|
||||
# invalid uuid -> ignore
|
||||
json_export['data'][0]['uuid'] = 'hello world'
|
||||
|
@ -1165,7 +1329,7 @@ def test_backoffice_cards_update_data_from_json(pub):
|
|||
resp = resp.follow()
|
||||
assert carddef.data_class().count() == 5
|
||||
assert len({x.uuid for x in carddef.data_class().select()}) == 5 # all unique UUIDs
|
||||
assert carddef.data_class().get_by_uuid(card.uuid).data == {'1': 'plop 2'}
|
||||
assert carddef.data_class().get_by_uuid(card.uuid).data == {'1': 'plop 2', 'bo1': None}
|
||||
|
||||
# update and change status
|
||||
json_export['data'][0]['uuid'] = str(card.uuid)
|
||||
|
@ -1186,6 +1350,118 @@ def test_backoffice_cards_update_data_from_json(pub):
|
|||
assert [x for x in card.iter_evolution_parts(ContentSnapshotPart)][-1].user_id == user.id
|
||||
|
||||
|
||||
def test_backoffice_cards_update_data_from_json_custom_id(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
workflow = CardDef.get_default_workflow()
|
||||
workflow.id = None
|
||||
workflow.add_status('status2', 'st2')
|
||||
workflow.store()
|
||||
|
||||
CardDef.wipe()
|
||||
carddef = CardDef()
|
||||
carddef.name = 'test'
|
||||
carddef.fields = [
|
||||
fields.StringField(id='1', label='Test', varname='string'),
|
||||
fields.StringField(id='2', label='Custom id', varname='custom_id'),
|
||||
]
|
||||
carddef.id_template = '{{form_var_custom_id}}'
|
||||
carddef.workflow_roles = {'_editor': user.roles[0]}
|
||||
carddef.workflow_id = workflow.id
|
||||
carddef.backoffice_submission_roles = user.roles
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
|
||||
card = carddef.data_class()()
|
||||
card.data = {'1': 'plop', '2': 'test'}
|
||||
card.just_created()
|
||||
card.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get(carddef.get_url())
|
||||
resp = resp.click('Export Data')
|
||||
resp.form['format'] = 'json'
|
||||
resp = resp.form.submit('submit')
|
||||
job_id = urllib.parse.parse_qs(urllib.parse.urlparse(resp.location).query)['job'][0]
|
||||
job = AfterJob.get(job_id)
|
||||
json_export = json.loads(job.file_content)
|
||||
assert len(json_export['data']) == 1
|
||||
|
||||
# update
|
||||
del json_export['data'][0]['uuid'] # ignore uuid
|
||||
json_export['data'][0]['fields']['string'] = 'plop 2'
|
||||
resp = app.get(carddef.get_url())
|
||||
resp = resp.click('Import data from a file')
|
||||
resp.forms[0]['file'] = Upload('test.json', json.dumps(json_export).encode(), 'application/json')
|
||||
resp.form['update_existing_cards'].checked = True
|
||||
resp = resp.forms[0].submit()
|
||||
assert '/backoffice/processing?job=' in resp.location
|
||||
resp = resp.follow()
|
||||
|
||||
assert carddef.data_class().count() == 1
|
||||
card.refresh_from_storage()
|
||||
assert card.data == {'1': 'plop 2', '2': 'test'}
|
||||
|
||||
# no id and no uuid, but an existing id will be computed, -> update
|
||||
json_export['data'][0]['id'] = None
|
||||
json_export['data'][0]['uuid'] = None
|
||||
json_export['data'][0]['fields']['string'] = 'plop 3'
|
||||
resp = app.get(carddef.get_url())
|
||||
resp = resp.click('Import data from a file')
|
||||
resp.forms[0]['file'] = Upload('test.json', json.dumps(json_export).encode(), 'application/json')
|
||||
resp.form['update_existing_cards'].checked = True
|
||||
resp = resp.forms[0].submit()
|
||||
assert '/backoffice/processing?job=' in resp.location
|
||||
resp = resp.follow()
|
||||
assert carddef.data_class().count() == 1
|
||||
card.refresh_from_storage()
|
||||
assert card.data == {'1': 'plop 3', '2': 'test'}
|
||||
|
||||
# different id -> create new one, and id will then be updated according to template
|
||||
json_export['data'][0]['id'] = 'hello'
|
||||
json_export['data'][0]['fields']['custom_id'] = 'he' # doesn't match
|
||||
json_export['data'][0]['fields']['string'] = 'plop 4'
|
||||
resp = app.get(carddef.get_url())
|
||||
resp = resp.click('Import data from a file')
|
||||
resp.forms[0]['file'] = Upload('test.json', json.dumps(json_export).encode(), 'application/json')
|
||||
resp.form['update_existing_cards'].checked = True
|
||||
resp = resp.forms[0].submit()
|
||||
assert '/backoffice/processing?job=' in resp.location
|
||||
resp = resp.follow()
|
||||
assert carddef.data_class().count() == 2
|
||||
assert carddef.data_class().get_by_id('he').data == {'1': 'plop 4', '2': 'he'}
|
||||
|
||||
# asked not to update, but same id, it should be skipped
|
||||
json_export['data'][0]['id'] = str(card.id)
|
||||
json_export['data'][0]['fields']['string'] = 'plop 6'
|
||||
resp = app.get(carddef.get_url())
|
||||
resp = resp.click('Import data from a file')
|
||||
resp.forms[0]['file'] = Upload('test.json', json.dumps(json_export).encode(), 'application/json')
|
||||
resp.form['update_existing_cards'].checked = False
|
||||
resp = resp.forms[0].submit()
|
||||
assert '/backoffice/processing?job=' in resp.location
|
||||
resp = resp.follow()
|
||||
assert carddef.data_class().count() == 2
|
||||
card.refresh_from_storage()
|
||||
assert card.data == {'1': 'plop 3', '2': 'test'}
|
||||
|
||||
# asked not to update, but same id would be computed, it should be skipped
|
||||
json_export['data'][0]['id'] = None
|
||||
json_export['data'][0]['uuid'] = None
|
||||
json_export['data'][0]['fields']['string'] = 'plop 7'
|
||||
json_export['data'][0]['fields']['custom_id'] = 'test'
|
||||
resp = app.get(carddef.get_url())
|
||||
resp = resp.click('Import data from a file')
|
||||
resp.forms[0]['file'] = Upload('test.json', json.dumps(json_export).encode(), 'application/json')
|
||||
resp.form['update_existing_cards'].checked = False
|
||||
resp = resp.forms[0].submit()
|
||||
assert '/backoffice/processing?job=' in resp.location
|
||||
resp = resp.follow()
|
||||
assert carddef.data_class().count() == 2
|
||||
card.refresh_from_storage()
|
||||
assert card.data == {'1': 'plop 3', '2': 'test'}
|
||||
|
||||
|
||||
def test_backoffice_cards_wscall_failure_display(http_requests, pub):
|
||||
user = create_user(pub)
|
||||
|
||||
|
|
|
@ -1173,21 +1173,25 @@ def test_backoffice_custom_view_sort_field(pub):
|
|||
items=['foo', 'bar', 'baz'],
|
||||
display_locations=['validation', 'summary', 'listings'],
|
||||
),
|
||||
fields.StringField(
|
||||
id='2',
|
||||
label='field 2',
|
||||
),
|
||||
]
|
||||
formdef.workflow_roles = {'_receiver': 1}
|
||||
formdef.store()
|
||||
|
||||
formdef.data_class().wipe()
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'1': 'foo', '1_display': 'foo'}
|
||||
formdata.data = {'1': 'foo', '1_display': 'foo', '2': 'foo foo'}
|
||||
formdata.jump_status('new')
|
||||
formdata.store()
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'1': 'bar', '1_display': 'bar'}
|
||||
formdata.data = {'1': 'bar', '1_display': 'bar', '2': 'foo foo'}
|
||||
formdata.jump_status('new')
|
||||
formdata.store()
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'1': 'baz', '1_display': 'baz'}
|
||||
formdata.data = {'1': 'baz', '1_display': 'baz', '2': 'foo'}
|
||||
formdata.jump_status('new')
|
||||
formdata.store()
|
||||
|
||||
|
@ -1220,6 +1224,19 @@ def test_backoffice_custom_view_sort_field(pub):
|
|||
resp = app.get('/backoffice/management/form-title/shared-custom-test-view/')
|
||||
assert resp.text.count('<tr') == 4
|
||||
|
||||
# check rank takes over when searching on text
|
||||
custom_view.order_by = '-f1'
|
||||
custom_view.store()
|
||||
resp = app.get('/backoffice/management/form-title/shared-custom-test-view/')
|
||||
resp.forms['listing-settings']['q'] = 'foo'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert re.findall(r'<a href="(\d)/">1-(\d)</a>', resp.text) == [('1', '1'), ('2', '2'), ('3', '3')]
|
||||
|
||||
# but can still be overridden by query string
|
||||
resp.forms['listing-settings']['order_by'] = '-f1'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert re.findall(r'<a href="(\d)/">1-(\d)</a>', resp.text) == [('1', '1'), ('3', '3'), ('2', '2')]
|
||||
|
||||
|
||||
def test_carddata_custom_view(pub):
|
||||
user = create_user(pub)
|
||||
|
|
|
@ -8,6 +8,7 @@ import xml.etree.ElementTree as ET
|
|||
import zipfile
|
||||
|
||||
import pytest
|
||||
from django.utils.timezone import make_aware
|
||||
|
||||
from wcs import fields
|
||||
from wcs.blocks import BlockDef
|
||||
|
@ -75,7 +76,7 @@ def test_backoffice_csv(pub):
|
|||
formdef.data_class().wipe()
|
||||
for i in range(3):
|
||||
formdata = formdef.data_class()()
|
||||
formdata.receipt_time = datetime.datetime(2015, 1, 1).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2015, 1, 1))
|
||||
formdata.data = {'1': 'FOO BAR %d' % i}
|
||||
if i == 0:
|
||||
formdata.data['2'] = 'foo'
|
||||
|
@ -216,8 +217,8 @@ def test_backoffice_csv(pub):
|
|||
assert resp_csv.text.splitlines() == [
|
||||
'"3rd field (identifier)","3rd field"',
|
||||
'"foo",""',
|
||||
'"A","aa"',
|
||||
'"C","cc"',
|
||||
'"A","aa"',
|
||||
]
|
||||
|
||||
|
||||
|
@ -247,7 +248,7 @@ def test_backoffice_export_long_listings(pub, threshold):
|
|||
formdef.data_class().wipe()
|
||||
for i in range(2):
|
||||
formdata = formdef.data_class()()
|
||||
formdata.receipt_time = datetime.datetime(2015, 1, 1).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2015, 1, 1))
|
||||
formdata.data = {'1': 'BAZ BAZ %d' % i}
|
||||
formdata.jump_status('new')
|
||||
formdata.store()
|
||||
|
@ -264,8 +265,8 @@ def test_backoffice_export_long_listings(pub, threshold):
|
|||
resp_lines = resp.text.splitlines()
|
||||
assert resp_lines[0] == '"Number","Created","Last Modified","User Label","1st field","Status"'
|
||||
assert len(resp_lines) == 3
|
||||
assert resp_lines[1].split(',')[1].startswith('"' + time.strftime('%Y-%m-%d', formdata.receipt_time))
|
||||
assert resp_lines[1].split(',')[2].startswith('"' + time.strftime('%Y-%m-%d', formdata.last_update_time))
|
||||
assert resp_lines[1].split(',')[1].startswith('"' + formdata.receipt_time.strftime('%Y-%m-%d'))
|
||||
assert resp_lines[1].split(',')[2].startswith('"' + formdata.last_update_time.strftime('%Y-%m-%d'))
|
||||
|
||||
resp = app.get('/backoffice/management/form-title/')
|
||||
resp = resp.click('Export a Spreadsheet')
|
||||
|
@ -1117,7 +1118,7 @@ def test_backoffice_header_line(pub):
|
|||
formdef.data_class().wipe()
|
||||
for i in range(3):
|
||||
formdata = formdef.data_class()()
|
||||
formdata.receipt_time = datetime.datetime(2015, 1, 1).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2015, 1, 1))
|
||||
formdata.data = {'1': 'FOO BAR %d' % i}
|
||||
formdata.jump_status('new')
|
||||
formdata.store()
|
||||
|
|
|
@ -12,7 +12,8 @@ from wcs.carddef import CardDef
|
|||
from wcs.data_sources import NamedDataSource
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef
|
||||
from wcs.wf.criticality import MODE_INC
|
||||
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef, WorkflowCriticalityLevel
|
||||
|
||||
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
|
||||
from .test_all import create_environment, create_superuser
|
||||
|
@ -1048,6 +1049,7 @@ def test_backoffice_string_filter(pub):
|
|||
'not_in',
|
||||
'absent',
|
||||
'existing',
|
||||
'icontains',
|
||||
]
|
||||
|
||||
resp.forms['listing-settings']['filter-4-value'].value = 'a'
|
||||
|
@ -1167,6 +1169,7 @@ def test_backoffice_text_filter(pub):
|
|||
'not_in',
|
||||
'absent',
|
||||
'existing',
|
||||
'icontains',
|
||||
]
|
||||
|
||||
resp.forms['listing-settings']['filter-4-value'].value = 'a'
|
||||
|
@ -1227,6 +1230,7 @@ def test_backoffice_email_filter(pub):
|
|||
'not_in',
|
||||
'absent',
|
||||
'existing',
|
||||
'icontains',
|
||||
]
|
||||
|
||||
resp.forms['listing-settings']['filter-4-value'].value = 'a@localhost'
|
||||
|
@ -1245,6 +1249,12 @@ def test_backoffice_email_filter(pub):
|
|||
assert resp.text.count('>a@localhost</') > 0
|
||||
assert resp.text.count('>b@localhost</') == 0
|
||||
|
||||
resp.forms['listing-settings']['filter-4-value'].value = 'a@local'
|
||||
resp.forms['listing-settings']['filter-4-operator'].value = 'icontains'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert resp.text.count('>a@localhost</') > 0
|
||||
assert resp.text.count('>b@localhost</') == 0
|
||||
|
||||
|
||||
def test_backoffice_date_filter(pub):
|
||||
pub.user_class.wipe()
|
||||
|
@ -1761,6 +1771,7 @@ def test_backoffice_block_field_filter(pub):
|
|||
'not_in',
|
||||
'absent',
|
||||
'existing',
|
||||
'icontains',
|
||||
]
|
||||
resp.forms['listing-settings']['filter-0-1-value'].value = 'plop0'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
|
@ -1879,6 +1890,7 @@ def test_backoffice_block_field_filter(pub):
|
|||
'not_in',
|
||||
'absent',
|
||||
'existing',
|
||||
'icontains',
|
||||
]
|
||||
resp.forms['listing-settings']['filter-0-5-value'].value = 'a@localhost'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
|
@ -1893,6 +1905,10 @@ def test_backoffice_block_field_filter(pub):
|
|||
resp.forms['listing-settings']['filter-0-5-operator'].value = 'ne'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert resp.text.count('<tr') == 1 + 4
|
||||
resp.forms['listing-settings']['filter-0-5-value'].value = '@localhost'
|
||||
resp.forms['listing-settings']['filter-0-5-operator'].value = 'icontains'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert resp.text.count('<tr') == 1 + 10
|
||||
|
||||
# mix
|
||||
resp = app.get('/backoffice/management/form-title/')
|
||||
|
@ -1975,3 +1991,76 @@ def test_backoffice_numeric_filter(pub):
|
|||
resp = resp.forms['listing-settings'].submit()
|
||||
assert resp.text.count('<td>123.4</td>') == 0
|
||||
assert resp.text.count('<td>315</td>') > 0
|
||||
|
||||
|
||||
def test_backoffice_criticality_filter(pub):
|
||||
pub.user_class.wipe()
|
||||
create_superuser(pub)
|
||||
pub.role_class.wipe()
|
||||
role = pub.role_class(name='test')
|
||||
role.store()
|
||||
|
||||
Workflow.wipe()
|
||||
workflow = Workflow(name='test')
|
||||
workflow.criticality_levels = [
|
||||
WorkflowCriticalityLevel(name='green'),
|
||||
WorkflowCriticalityLevel(name='yellow'),
|
||||
WorkflowCriticalityLevel(name='red'),
|
||||
WorkflowCriticalityLevel(name='black'),
|
||||
]
|
||||
workflow.add_status('st1')
|
||||
st2 = workflow.add_status('st2')
|
||||
action = st2.add_action('modify_criticality')
|
||||
action.mode = MODE_INC
|
||||
st3 = workflow.add_status('st3')
|
||||
action = st3.add_action('modify_criticality')
|
||||
action.mode = MODE_INC
|
||||
action = st3.add_action('modify_criticality')
|
||||
action.mode = MODE_INC
|
||||
workflow.store()
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form-title'
|
||||
formdef.fields = [
|
||||
fields.StringField(id='1', label='Test', type='string', display_locations=['listings']),
|
||||
]
|
||||
formdef.workflow_roles = {'_receiver': role.id}
|
||||
formdef.workflow = workflow
|
||||
formdef.store()
|
||||
|
||||
data_class = formdef.data_class()
|
||||
data_class.wipe()
|
||||
|
||||
for i in range(3):
|
||||
formdata = data_class()
|
||||
formdata.data = {'1': f'baz{i}'}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
if i == 0:
|
||||
formdata.jump_status(st2.id)
|
||||
else:
|
||||
formdata.jump_status(st3.id)
|
||||
formdata.perform_workflow()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/management/form-title/')
|
||||
resp.forms['listing-settings']['filter'] = 'all'
|
||||
resp.forms['listing-settings']['filter-criticality-level'].checked = True
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert resp.text.count('<td>baz') == 3
|
||||
resp.forms['listing-settings']['filter-criticality-level-value'] = '0'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert resp.text.count('<td>baz') == 0
|
||||
resp.forms['listing-settings']['filter-criticality-level-value'] = '1'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert resp.text.count('<td>baz') == 1
|
||||
resp.forms['listing-settings']['filter-criticality-level-value'] = '2'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert resp.text.count('<td>baz') == 2
|
||||
resp.forms['listing-settings']['filter-criticality-level-value'] = '3'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert resp.text.count('<td>baz') == 0
|
||||
resp.forms['listing-settings']['filter-criticality-level-value'] = ''
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert resp.text.count('<td>baz') == 3
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import datetime
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
|
||||
import pytest
|
||||
from django.utils.timezone import localtime
|
||||
from pyquery import PyQuery
|
||||
|
||||
from wcs import fields
|
||||
|
@ -20,6 +22,7 @@ from wcs.workflows import (
|
|||
Workflow,
|
||||
WorkflowBackofficeFieldsFormDef,
|
||||
WorkflowCriticalityLevel,
|
||||
WorkflowVariablesFieldsFormDef,
|
||||
)
|
||||
|
||||
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
|
||||
|
@ -733,7 +736,7 @@ def test_inspect_page_with_related_objects(pub):
|
|||
|
||||
|
||||
def test_inspect_page_actions_traces(pub):
|
||||
create_user(pub, is_admin=True)
|
||||
user = create_user(pub, is_admin=True)
|
||||
|
||||
FormDef.wipe()
|
||||
target_formdef = FormDef()
|
||||
|
@ -792,6 +795,10 @@ def test_inspect_page_actions_traces(pub):
|
|||
edit_carddata2.varname = 'edited_card_wrong_target_id'
|
||||
edit_carddata2.mappings = [Mapping(field_id='0', expression='foo bar blah')]
|
||||
|
||||
choice = st1.items[2]
|
||||
assert choice.key == 'choice'
|
||||
choice.by = ['logged-users']
|
||||
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
|
@ -801,21 +808,27 @@ def test_inspect_page_actions_traces(pub):
|
|||
|
||||
formdef.data_class().wipe()
|
||||
formdata = formdef.data_class()()
|
||||
formdata.user_id = user.id
|
||||
formdata.data = {}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.record_workflow_event('frontoffice-created')
|
||||
formdata.perform_workflow()
|
||||
formdata.store()
|
||||
app = login(get_app(pub))
|
||||
resp = app.get(formdata.get_url())
|
||||
resp = resp.form.submit('button_accept')
|
||||
resp = resp.follow()
|
||||
formdata.refresh_from_storage()
|
||||
assert formdata.status == 'wf-accepted'
|
||||
|
||||
# change receipt time to get global timeout to run
|
||||
formdata.receipt_time = time.localtime(time.time() - 3 * 86400)
|
||||
formdata.receipt_time = localtime() - datetime.timedelta(days=3)
|
||||
formdata.store()
|
||||
pub.apply_global_action_timeouts()
|
||||
formdata.refresh_from_storage()
|
||||
assert formdata.get_criticality_level_object().name == 'yellow'
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get(formdata.get_url(backoffice=True), status=200)
|
||||
resp = resp.click('Data Inspector')
|
||||
assert '>Actions Tracing</' in resp
|
||||
|
@ -825,9 +838,10 @@ def test_inspect_page_actions_traces(pub):
|
|||
'Created form - target form #1-1',
|
||||
'Created card - target card #1-1',
|
||||
'Edited card - target card #1-1',
|
||||
'Action button - Manual Jump Accept',
|
||||
'Global action timeout',
|
||||
]
|
||||
assert [x.text for x in resp.pyquery('#inspect-timeline strong')] == ['Just Submitted', 'New']
|
||||
assert [x.text for x in resp.pyquery('#inspect-timeline strong')] == ['Just Submitted', 'New', 'Accepted']
|
||||
assert [x.text for x in resp.pyquery('#inspect-timeline a.tracing-link') if x.text] == [
|
||||
'Email',
|
||||
'Email',
|
||||
|
@ -836,6 +850,8 @@ def test_inspect_page_actions_traces(pub):
|
|||
'Create Card Data',
|
||||
'Edit Card Data',
|
||||
'Edit Card Data',
|
||||
'Email',
|
||||
'Email',
|
||||
'Criticality Levels',
|
||||
]
|
||||
event_links = [x.attrib['href'] for x in resp.pyquery('#inspect-timeline .event a')]
|
||||
|
@ -843,6 +859,7 @@ def test_inspect_page_actions_traces(pub):
|
|||
'http://example.net/backoffice/management/target-form/1/', # Created form
|
||||
'http://example.net/backoffice/data/target-card/1/', # Created card
|
||||
'http://example.net/backoffice/data/target-card/1/', # Edited card
|
||||
'http://example.net/backoffice/workflows/2/status/new/items/_accept/', # Accept manual jump
|
||||
'http://example.net/backoffice/workflows/2/global-actions/1/#trigger-%s' % trigger.id,
|
||||
]
|
||||
# check all links are valid
|
||||
|
@ -852,19 +869,21 @@ def test_inspect_page_actions_traces(pub):
|
|||
'Created form - target form #1-1',
|
||||
'Created card - target card #1-1',
|
||||
'Edited card - target card #1-1',
|
||||
'Action button - Manual Jump Accept',
|
||||
'Global action timeout',
|
||||
]
|
||||
assert [x.text for x in resp.pyquery('#inspect-timeline .event-error')] == ['Nothing edited']
|
||||
action_links = [x.attrib['href'] for x in resp.pyquery('#inspect-timeline a.tracing-link')]
|
||||
assert len(action_links) == 13
|
||||
assert action_links[0] == 'http://example.net/backoffice/workflows/2/status/just_submitted/'
|
||||
assert (
|
||||
action_links[1]
|
||||
== 'http://example.net/backoffice/workflows/2/status/just_submitted/items/_notify_new_receiver_email/'
|
||||
)
|
||||
assert action_links[-5] == 'http://example.net/backoffice/workflows/2/status/new/items/_create/'
|
||||
assert action_links[-4] == 'http://example.net/backoffice/workflows/2/status/new/items/_create_card/'
|
||||
assert action_links[-3] == 'http://example.net/backoffice/workflows/2/status/new/items/_edit_card/'
|
||||
assert action_links[-2] == 'http://example.net/backoffice/workflows/2/status/new/items/_edit_card2/'
|
||||
assert action_links[-8] == 'http://example.net/backoffice/workflows/2/status/new/items/_create/'
|
||||
assert action_links[-7] == 'http://example.net/backoffice/workflows/2/status/new/items/_create_card/'
|
||||
assert action_links[-6] == 'http://example.net/backoffice/workflows/2/status/new/items/_edit_card/'
|
||||
assert action_links[-5] == 'http://example.net/backoffice/workflows/2/status/new/items/_edit_card2/'
|
||||
assert action_links[-1] == 'http://example.net/backoffice/workflows/2/global-actions/1/items/1/'
|
||||
|
||||
# check details are available
|
||||
|
@ -889,6 +908,7 @@ def test_inspect_page_actions_traces(pub):
|
|||
'Created form - target form #1-1',
|
||||
'Created card - deleted',
|
||||
'Edited card - deleted',
|
||||
'Action button - Manual Jump Accept',
|
||||
'Global action timeout',
|
||||
]
|
||||
|
||||
|
@ -1008,10 +1028,153 @@ def test_inspect_page_lazy_list(pub):
|
|||
resp.form['template'] = '{{ form_objects|order_by:"string"|getlist:"string" }}'
|
||||
resp = resp.form.submit()
|
||||
assert 'rendered as an object' in resp.text
|
||||
assert 'Also rendered as an iterable' not in resp.text
|
||||
assert resp.pyquery('.test-tool-lazylist-details li:first-child').text() == 'Number of items: 3'
|
||||
assert resp.pyquery('.test-tool-lazylist-details li:last-child').text() == 'First items: bar, baz, foo'
|
||||
|
||||
|
||||
def test_inspect_page_iterable(pub):
|
||||
FormDef.wipe()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'bar'
|
||||
formdef.fields = [
|
||||
fields.ItemsField(id='1', label='Items', varname='items', items=['foo1', 'foo2', 'foo3'])
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {
|
||||
'1': ['foo1', 'foo3'],
|
||||
}
|
||||
formdata.data['1_display'] = formdef.fields[0].store_display_value(formdata.data, '1')
|
||||
formdata.data['1_structured'] = formdef.fields[0].store_structured_value(formdata.data, '1')
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
create_user(pub, is_admin=True)
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('%sinspect' % formdata.get_url(backoffice=True))
|
||||
|
||||
resp.form['test_mode'] = 'template'
|
||||
resp.form['template'] = '{{ form_var_items }}'
|
||||
resp = resp.form.submit()
|
||||
assert 'rendered as an object' in resp.text
|
||||
assert 'Also rendered as an iterable' in resp.text
|
||||
assert [PyQuery(result).text() for result in resp.pyquery('.test-tool-result-plain')] == [
|
||||
'foo1, foo3',
|
||||
'foo1, foo3 (string)',
|
||||
"['foo1', 'foo3'] (list)",
|
||||
]
|
||||
|
||||
# with carddef datasource
|
||||
carddef = CardDef()
|
||||
carddef.name = 'foo'
|
||||
carddef.fields = [fields.StringField(id='1', label='First name', varname='firstname')]
|
||||
carddef.digest_templates = {'default': '{{ form_var_firstname }}'}
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
carddatas = []
|
||||
for i in range(0, 3):
|
||||
carddata = carddef.data_class()()
|
||||
carddata.data = {
|
||||
'1': 'foo%s' % i,
|
||||
}
|
||||
carddata.just_created()
|
||||
carddata.store()
|
||||
carddatas.append(carddata)
|
||||
|
||||
formdef.fields[0].data_source = {'type': 'carddef:%s' % carddef.url_name}
|
||||
formdef.fields[0].items = None
|
||||
formdef.store()
|
||||
formdata.data = {
|
||||
'1': ['1', '3'],
|
||||
}
|
||||
formdata.data['1_display'] = formdef.fields[0].store_display_value(formdata.data, '1')
|
||||
formdata.data['1_structured'] = formdef.fields[0].store_structured_value(formdata.data, '1')
|
||||
formdata.store()
|
||||
|
||||
resp = resp.form.submit()
|
||||
assert 'rendered as an object' in resp.text
|
||||
assert 'Also rendered as an iterable' in resp.text
|
||||
results = [PyQuery(result).text() for result in resp.pyquery('.test-tool-result-plain')]
|
||||
assert len(results) == 3
|
||||
assert results[0] == 'foo0, foo2'
|
||||
assert results[1] == 'foo0, foo2 (string)'
|
||||
assert '[<wcs.variables.LazyFormData object' in results[2]
|
||||
assert '] (list)' in results[2]
|
||||
|
||||
# with json value
|
||||
datasource = {
|
||||
'type': 'jsonvalue',
|
||||
'value': json.dumps(
|
||||
[
|
||||
{'id': 'b', 'text': 'baker', 'extra': 'plop'},
|
||||
{'id': 'c', 'text': 'cook', 'extra': 'plop2'},
|
||||
{'id': 'l', 'text': 'lawyer', 'extra': 'plop3'},
|
||||
]
|
||||
),
|
||||
}
|
||||
formdef.fields[0].data_source = datasource
|
||||
formdef.store()
|
||||
formdata.data = {
|
||||
'1': ['b', 'l'],
|
||||
}
|
||||
formdata.data['1_display'] = formdef.fields[0].store_display_value(formdata.data, '1')
|
||||
formdata.data['1_structured'] = formdef.fields[0].store_structured_value(formdata.data, '1')
|
||||
formdata.store()
|
||||
|
||||
resp = resp.form.submit()
|
||||
assert 'rendered as an object' in resp.text
|
||||
assert 'Also rendered as an iterable' in resp.text
|
||||
assert [PyQuery(result).text() for result in resp.pyquery('.test-tool-result-plain')] == [
|
||||
'',
|
||||
'(string)',
|
||||
"[{'id': 'b', 'text': 'baker', 'extra': 'plop'}, {'id': 'l', 'text': 'lawyer', 'extra': 'plop3'}] (list)",
|
||||
]
|
||||
|
||||
# check with block field
|
||||
BlockDef.wipe()
|
||||
|
||||
block = BlockDef()
|
||||
block.name = 'Child'
|
||||
block.fields = [fields.StringField(id='123', required=True, label='First name', varname='firstname')]
|
||||
block.digest_template = '{{ child_var_firstname }}'
|
||||
block.store()
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'bar'
|
||||
formdef.fields = [fields.BlockField(id='1', label='Children', block_slug='child', varname='children')]
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {
|
||||
'1': {
|
||||
'data': [{'123': 'first1'}, {'123': 'first2'}],
|
||||
'schema': {'123': 'string'},
|
||||
},
|
||||
'1_display': 'foo, bar',
|
||||
}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
resp = app.get('%sinspect' % formdata.get_url(backoffice=True))
|
||||
resp.form['test_mode'] = 'template'
|
||||
resp.form['template'] = '{{ form_var_children }}'
|
||||
resp = resp.form.submit()
|
||||
assert 'rendered as an object' in resp.text
|
||||
assert 'Also rendered as an iterable' in resp.text
|
||||
results = [PyQuery(result).text() for result in resp.pyquery('.test-tool-result-plain')]
|
||||
assert len(results) == 3
|
||||
assert results[0] == 'foo, bar'
|
||||
assert (
|
||||
results[1] == "{'data': [{'123': 'first1'}, {'123': 'first2'}], 'schema': {'123': 'string'}} (dict)"
|
||||
)
|
||||
assert '[<wcs.variables.LazyBlockDataVar object' in results[2]
|
||||
assert '] (list)' in results[2]
|
||||
|
||||
|
||||
def test_inspect_page_idp_role(pub):
|
||||
create_user(pub, is_admin=True)
|
||||
FormDef.wipe()
|
||||
|
@ -1043,3 +1206,72 @@ def test_inspect_page_idp_role(pub):
|
|||
resp.pyquery('[data-function-key="_receiver"] a').attr.href
|
||||
== 'https://idp.example.net/manage/roles/uuid:d4b59e1ffb204dfd99fd3760f4952999/'
|
||||
)
|
||||
|
||||
|
||||
def test_inspect_page_form_option(pub):
|
||||
create_user(pub, is_admin=True)
|
||||
FormDef.wipe()
|
||||
|
||||
wf = Workflow(name='variables')
|
||||
wf.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=wf)
|
||||
wf.add_status('st1')
|
||||
wf.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = []
|
||||
formdef.workflow = wf
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('%sinspect' % formdata.get_url(backoffice=True), status=200)
|
||||
assert 'form_option' not in resp.text
|
||||
|
||||
wf.variables_formdef.fields = [
|
||||
fields.StringField(label='String test', varname='string_test'),
|
||||
fields.DateField(label='Date test', varname='date_test'),
|
||||
]
|
||||
wf.store()
|
||||
resp = app.get('%sinspect' % formdata.get_url(backoffice=True), status=200)
|
||||
assert (
|
||||
resp.pyquery('[title="form_option_string_test"]').parents('li').children('div.value span').text()
|
||||
== 'None (no value)'
|
||||
)
|
||||
|
||||
wf.variables_formdef.fields[0].default_value = 'xxx'
|
||||
wf.variables_formdef.fields[1].default_value = '2024-03-20'
|
||||
wf.store()
|
||||
resp = app.get('%sinspect' % formdata.get_url(backoffice=True), status=200)
|
||||
assert (
|
||||
resp.pyquery('[title="form_option_string_test"]').parents('li').children('div.value span').text()
|
||||
== 'xxx'
|
||||
)
|
||||
assert (
|
||||
resp.pyquery('[title="form_option_date_test"]').parents('li').children('div.value span').text()
|
||||
== '2024-03-20'
|
||||
)
|
||||
assert (
|
||||
resp.pyquery('[title="form_option_date_test_year"]').parents('li').children('div.value span').text()
|
||||
== '2024 (integer number)'
|
||||
)
|
||||
|
||||
formdef.workflow_options = {'string_test': 'yyy', 'date_test': datetime.date(2024, 3, 21).timetuple()}
|
||||
formdef.store()
|
||||
resp = app.get('%sinspect' % formdata.get_url(backoffice=True), status=200)
|
||||
assert (
|
||||
resp.pyquery('[title="form_option_string_test"]').parents('li').children('div.value span').text()
|
||||
== 'yyy'
|
||||
)
|
||||
assert (
|
||||
resp.pyquery('[title="form_option_date_test"]').parents('li').children('div.value span').text()
|
||||
== '2024-03-21'
|
||||
)
|
||||
assert (
|
||||
resp.pyquery('[title="form_option_date_test_year"]').parents('li').children('div.value span').text()
|
||||
== '2024 (integer number)'
|
||||
)
|
||||
|
|
|
@ -27,7 +27,7 @@ def pub():
|
|||
'default_site_language': 'http',
|
||||
}
|
||||
pub.write_cfg()
|
||||
TranslatableMessage.do_table()
|
||||
TranslatableMessage.do_table() # update table with selected languages
|
||||
return pub
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import os
|
|||
import re
|
||||
|
||||
import pytest
|
||||
from django.utils.timezone import make_aware
|
||||
|
||||
from wcs import fields
|
||||
from wcs.blocks import BlockDef
|
||||
|
@ -64,9 +65,9 @@ def test_backoffice_listing_order(pub):
|
|||
formdata.jump_status('new')
|
||||
formdata.store()
|
||||
ids.append(formdata.id)
|
||||
formdata.receipt_time = datetime.datetime(2015, 1, 1, 10, i).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2015, 1, 1, 10, i))
|
||||
# ordered with odd-numbered ids then even-numbered ids
|
||||
formdata.evolution[-1].time = datetime.datetime(2015, 2, 1, 10 + i % 2, i).timetuple()
|
||||
formdata.evolution[-1].time = make_aware(datetime.datetime(2015, 2, 1, 10 + i % 2, i))
|
||||
formdata.store()
|
||||
|
||||
inversed_receipt_time_order = list(reversed([str(x) for x in sorted(ids)]))
|
||||
|
@ -142,7 +143,7 @@ def test_backoffice_criticality_in_formdef_listing_order(pub):
|
|||
formdata.data = {}
|
||||
formdata.just_created()
|
||||
formdata.jump_status('new')
|
||||
formdata.receipt_time = datetime.datetime(2015, 1, 1, 10, i).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2015, 1, 1, 10, i))
|
||||
if i < 8:
|
||||
if i % 3 == 0:
|
||||
formdata.set_criticality_level(1)
|
||||
|
|
|
@ -134,8 +134,28 @@ def test_backoffice_statistics_status_select(pub):
|
|||
resp.forms['listing-settings']['filter-%s-value' % field1.id].value = 'baz'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert 'Total number of records: 24' in resp.text
|
||||
assert resp.pyquery('ul.resolution-times.status-wf-new li')[0].text == 'Count: 8'
|
||||
|
||||
resp.forms['listing-settings']['filter-%s-value' % field1.id].value = 'baz'
|
||||
resp.forms['listing-settings']['filter-%s-operator' % field1.id].value = 'in'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert 'Total number of records: 24' in resp.text
|
||||
assert resp.pyquery('ul.resolution-times.status-wf-new li')[0].text == 'Count: 8'
|
||||
|
||||
resp.forms['listing-settings']['filter-%s-value' % field1.id].value = 'baz'
|
||||
resp.forms['listing-settings']['filter-%s-operator' % field1.id].value = 'not_in'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert 'Total number of records: 26' in resp.text
|
||||
assert resp.pyquery('ul.resolution-times.status-wf-new li')[0].text == 'Count: 9'
|
||||
|
||||
resp.forms['listing-settings']['filter-%s-value' % field1.id].value = 'baz'
|
||||
resp.forms['listing-settings']['filter-%s-operator' % field1.id].value = 'existing'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert 'Total number of records: 50' in resp.text
|
||||
assert resp.pyquery('ul.resolution-times.status-wf-new li')[0].text == 'Count: 17'
|
||||
|
||||
resp.forms['listing-settings']['filter-%s-value' % field1.id].value = 'foo'
|
||||
resp.forms['listing-settings']['filter-%s-operator' % field1.id].value = 'eq'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert 'Total number of records: 13' in resp.text
|
||||
assert '<h2>Filters</h2>' in resp.text
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import datetime
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
|
||||
import pytest
|
||||
import responses
|
||||
from django.utils.timezone import localtime, make_aware
|
||||
|
||||
from wcs import fields
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.qommon.ident.password_accounts import PasswordAccount
|
||||
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef
|
||||
from wcs.wscalls import NamedWsCall
|
||||
|
||||
|
@ -161,6 +162,52 @@ def test_backoffice_submission(pub):
|
|||
assert resp.location == 'http://www.example.org/'
|
||||
|
||||
|
||||
def test_backoffice_submission_menu_entry(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = []
|
||||
formdef.workflow_roles = {'_receiver': 1}
|
||||
formdef.backoffice_submission_roles = user.roles[:]
|
||||
formdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/management/forms')
|
||||
assert resp.pyquery('#sidepage-menu .icon-submission')
|
||||
|
||||
pub.cfg['backoffice-submission'] = {}
|
||||
pub.cfg['backoffice-submission']['sidebar_menu_entry'] = 'visible'
|
||||
pub.write_cfg()
|
||||
resp = app.get('/backoffice/submission/', status=200)
|
||||
assert resp.pyquery('#sidepage-menu .icon-submission')
|
||||
|
||||
pub.cfg['backoffice-submission']['sidebar_menu_entry'] = 'redirect'
|
||||
pub.cfg['backoffice-submission']['redirect'] = 'https://example.net/'
|
||||
pub.write_cfg()
|
||||
resp = app.get('/backoffice/management/forms')
|
||||
assert resp.pyquery('#sidepage-menu .icon-submission')
|
||||
resp = app.get('/backoffice/submission/', status=302)
|
||||
assert resp.location == 'https://example.net/'
|
||||
|
||||
pub.cfg['backoffice-submission']['sidebar_menu_entry'] = 'hidden'
|
||||
pub.write_cfg()
|
||||
resp = app.get('/backoffice/management/forms')
|
||||
assert not resp.pyquery('#sidepage-menu .icon-submission')
|
||||
resp = app.get('/backoffice/submission/', status=302)
|
||||
assert resp.location == 'https://example.net/'
|
||||
|
||||
pub.cfg['backoffice-submission'][
|
||||
'redirect'
|
||||
] = '{% if session_user_email == "admin@localhost" %}https://example.net/{% endif %}'
|
||||
pub.write_cfg()
|
||||
app.get('/backoffice/submission/', status=302) # redirection
|
||||
user.email = 'admin2@localhost'
|
||||
user.store()
|
||||
app.get('/backoffice/submission/', status=200) # native screen
|
||||
|
||||
|
||||
def test_backoffice_submission_with_tracking_code(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
|
@ -213,7 +260,7 @@ def test_backoffice_submission_with_tracking_code(pub):
|
|||
assert formdata.tracking_code in resp.text
|
||||
|
||||
# check access at a later time
|
||||
formdata.receipt_time = time.localtime(time.time() - 3600)
|
||||
formdata.receipt_time = localtime() - datetime.timedelta(hours=1)
|
||||
formdata.store()
|
||||
resp = app.get(formdata_location)
|
||||
assert formdata.tracking_code not in resp.text
|
||||
|
@ -389,8 +436,8 @@ def test_backoffice_parallel_submission(pub, autosave):
|
|||
formdata.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/submission/')
|
||||
assert 'Submission to complete' in resp.text
|
||||
resp = app.get('/backoffice/submission/pending')
|
||||
assert resp.pyquery('tbody tr')
|
||||
resp1 = app.get('/backoffice/submission/form-title/%s/' % formdata.id)
|
||||
resp1 = resp1.follow()
|
||||
resp2 = app.get('/backoffice/submission/form-title/%s/' % formdata.id)
|
||||
|
@ -600,13 +647,16 @@ def test_backoffice_submission_drafts(pub):
|
|||
tracking_code = data_class.select()[0].tracking_code
|
||||
|
||||
# stop here, go back to index
|
||||
pub.cfg['submission-channels'] = {'include-in-global-listing': True}
|
||||
pub.write_cfg()
|
||||
resp = app.get('/backoffice/submission/')
|
||||
assert '%s/%s' % (formdef.url_name, formdata_no) in resp.text
|
||||
assert '>#%s' % formdata_no in resp.text
|
||||
resp = resp.click('Pending submissions')
|
||||
assert resp.pyquery('tbody tr a').text() == formdata.get_display_name()
|
||||
assert resp.pyquery('tbody tr a')[0].attrib['href'] == f'{formdef.url_name}/{formdata_no}/'
|
||||
formdata.submission_channel = 'mail'
|
||||
formdata.store()
|
||||
resp = app.get('/backoffice/submission/')
|
||||
assert '>Mail #%s' % formdata_no in resp.text
|
||||
resp = app.get('/backoffice/submission/pending')
|
||||
assert resp.pyquery('tbody td:nth-child(1)').text() == 'Mail'
|
||||
|
||||
# check it can also be accessed using its final URL
|
||||
resp2 = app.get('/backoffice/management/%s/%s/' % (formdef.url_name, formdata_no))
|
||||
|
@ -628,6 +678,33 @@ def test_backoffice_submission_drafts(pub):
|
|||
assert resp.location == 'http://example.net/backoffice/management/form-title/%s/' % formdata_no
|
||||
|
||||
|
||||
def test_backoffice_draft_with_digest(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = [
|
||||
fields.StringField(id='1', label='1st field', varname='foo'),
|
||||
]
|
||||
formdef.backoffice_submission_roles = user.roles[:]
|
||||
formdef.digest_templates = {'default': 'digest: {{ form_var_foo }}'}
|
||||
formdef.workflow_roles = {'_receiver': 1}
|
||||
formdef.store()
|
||||
|
||||
formdef.data_class().wipe()
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'1': 'bar'}
|
||||
formdata.status = 'draft'
|
||||
formdata.backoffice_submission = True
|
||||
formdata.submission_agent_id = str(user.id)
|
||||
formdata.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/submission/pending')
|
||||
assert resp.pyquery('tbody td:nth-child(1)').text() == 'form title #1-1 digest: bar'
|
||||
|
||||
|
||||
def test_backoffice_submission_remove_drafts(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
|
@ -661,7 +738,7 @@ def test_backoffice_submission_remove_drafts(pub):
|
|||
formdata_no = formdata.id
|
||||
|
||||
# stop here, go back to the index
|
||||
resp = app.get('/backoffice/submission/')
|
||||
resp = app.get('/backoffice/submission/pending')
|
||||
resp = resp.click('#%s' % formdata_no)
|
||||
resp = resp.follow()
|
||||
|
||||
|
@ -673,7 +750,7 @@ def test_backoffice_submission_remove_drafts(pub):
|
|||
assert pub.tracking_code_class().count() == 1
|
||||
|
||||
# and this time for real
|
||||
resp = app.get('/backoffice/submission/')
|
||||
resp = app.get('/backoffice/submission/pending')
|
||||
resp = resp.click('#%s' % formdata_no)
|
||||
resp = resp.follow()
|
||||
resp = resp.click('Discard this form')
|
||||
|
@ -688,6 +765,163 @@ def test_backoffice_submission_remove_drafts(pub):
|
|||
resp = app.get('/backoffice/submission/form-title/remove/%s' % formdata.id, status=403)
|
||||
|
||||
|
||||
def test_backoffice_submission_drafts_store_page_id(pub, autosave):
|
||||
user = create_user(pub)
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = [
|
||||
fields.PageField(id='0', label='1st page'),
|
||||
fields.StringField(id='1', label='string 1'),
|
||||
fields.PageField(id='2', label='2nd page'),
|
||||
fields.StringField(id='3', label='string 2'),
|
||||
fields.PageField(id='4', label='3rd page'),
|
||||
fields.StringField(id='5', label='string 3'),
|
||||
]
|
||||
|
||||
formdef.backoffice_submission_roles = user.roles[:]
|
||||
formdef.workflow_roles = {'_receiver': 1}
|
||||
formdef.enable_tracking_codes = True
|
||||
formdef.store()
|
||||
first_page_id = formdef.fields[0].id
|
||||
second_page_id = formdef.fields[2].id
|
||||
third_page_id = formdef.fields[4].id
|
||||
data_class = formdef.data_class()
|
||||
data_class.wipe()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/submission/')
|
||||
resp = resp.click(formdef.name)
|
||||
resp.form['f1'] = 'test'
|
||||
app.post('/backoffice/submission/form-title/autosave', params=resp.form.submit_fields())
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '0'
|
||||
assert formdata.page_id == first_page_id
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] is None
|
||||
assert formdata.data['5'] is None
|
||||
|
||||
resp = resp.form.submit('submit')
|
||||
# first page submitted, the draft in on the seconde page
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '1'
|
||||
assert formdata.page_id == second_page_id
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] is None
|
||||
assert formdata.data['5'] is None
|
||||
|
||||
resp.form['f3'] = 'foo'
|
||||
# autosave
|
||||
app.post('/backoffice/submission/form-title/autosave', params=resp.form.submit_fields())
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '1'
|
||||
assert formdata.page_id == second_page_id
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] == 'foo'
|
||||
assert formdata.data['5'] is None
|
||||
|
||||
resp = resp.form.submit('submit')
|
||||
# second page submitted, the draft in on the third page
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '2'
|
||||
assert formdata.page_id == third_page_id
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] == 'foo'
|
||||
assert formdata.data['5'] is None
|
||||
|
||||
resp.form['f5'] = 'bar'
|
||||
# autosave
|
||||
app.post('/backoffice/submission/form-title/autosave', params=resp.form.submit_fields())
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '2'
|
||||
assert formdata.page_id == third_page_id
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] == 'foo'
|
||||
assert formdata.data['5'] == 'bar'
|
||||
|
||||
resp = resp.form.submit('submit')
|
||||
# third page submitted, the draft in on the confirmation page
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '3'
|
||||
assert formdata.page_id == '_confirmation_page'
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] == 'foo'
|
||||
assert formdata.data['5'] == 'bar'
|
||||
|
||||
resp = resp.form.submit('previous')
|
||||
# back to third page
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '2'
|
||||
assert formdata.page_id == third_page_id
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] == 'foo'
|
||||
assert formdata.data['5'] == 'bar'
|
||||
|
||||
|
||||
def test_backoffice_submission_drafts_store_page_id_when_no_page(pub, autosave):
|
||||
user = create_user(pub)
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = [
|
||||
fields.StringField(id='1', label='string 1'),
|
||||
fields.StringField(id='2', label='string 2'),
|
||||
]
|
||||
formdef.backoffice_submission_roles = user.roles[:]
|
||||
formdef.workflow_roles = {'_receiver': 1}
|
||||
formdef.enable_tracking_codes = True
|
||||
formdef.store()
|
||||
data_class = formdef.data_class()
|
||||
data_class.wipe()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/submission/')
|
||||
resp = resp.click(formdef.name)
|
||||
resp.form['f1'] = 'test'
|
||||
resp.form['f2'] = 'bar'
|
||||
|
||||
# autosave
|
||||
app.post('/backoffice/submission/form-title/autosave', params=resp.form.submit_fields())
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '0'
|
||||
assert formdata.page_id == '_first_page'
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['2'] == 'bar'
|
||||
|
||||
resp = resp.form.submit('submit')
|
||||
# fields submitted, the draft in on the confirmation page
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '1'
|
||||
assert formdata.page_id == '_confirmation_page'
|
||||
assert formdata.data['1'] == 'test'
|
||||
|
||||
# back to first page
|
||||
resp = resp.form.submit('previous')
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '0'
|
||||
assert formdata.page_id == '_first_page'
|
||||
assert formdata.data['1'] == 'test'
|
||||
|
||||
|
||||
def test_backoffice_submission_live_condition(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
|
@ -794,48 +1028,6 @@ def test_backoffice_submission_conditional_jump_based_on_bo_field(pub):
|
|||
assert formdef.data_class().select()[0].status == 'wf-st1'
|
||||
|
||||
|
||||
def test_backoffice_submission_sections(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = [
|
||||
fields.StringField(
|
||||
id='1', label='1st field', display_locations=['validation', 'summary', 'listings']
|
||||
),
|
||||
]
|
||||
formdef.backoffice_submission_roles = user.roles[:]
|
||||
formdef.store()
|
||||
|
||||
data_class = formdef.data_class()
|
||||
data_class.wipe()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/submission/')
|
||||
assert 'Submission to complete' not in resp.text
|
||||
assert 'Running submission' not in resp.text
|
||||
|
||||
formdata = data_class()
|
||||
formdata.data = {}
|
||||
formdata.status = 'draft'
|
||||
formdata.backoffice_submission = True
|
||||
formdata.receipt_time = datetime.datetime(2015, 1, 1).timetuple()
|
||||
formdata.store()
|
||||
|
||||
resp = app.get('/backoffice/submission/')
|
||||
assert 'Submission to complete' in resp.text
|
||||
assert 'Running submission' not in resp.text
|
||||
assert '>#%s' % formdata.id in resp.text
|
||||
|
||||
formdata.data = {'1': 'xxx'}
|
||||
formdata.store()
|
||||
resp = app.get('/backoffice/submission/')
|
||||
assert 'Submission to complete' not in resp.text
|
||||
assert 'Running submission' in resp.text
|
||||
assert '>#%s' % formdata.id in resp.text
|
||||
|
||||
|
||||
def test_backoffice_submission_drafts_order(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
|
@ -854,28 +1046,39 @@ def test_backoffice_submission_drafts_order(pub):
|
|||
data_class.wipe()
|
||||
|
||||
formdata_ids = []
|
||||
for i in range(10):
|
||||
for i in range(25):
|
||||
formdata = data_class()
|
||||
formdata.data = {}
|
||||
formdata.status = 'draft'
|
||||
formdata.backoffice_submission = True
|
||||
formdata.receipt_time = datetime.datetime(2023, 11, 20 - i).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2023, 11, 30 - i))
|
||||
formdata.store()
|
||||
formdata_ids.append(formdata.id)
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/submission/')
|
||||
assert [x.attrib['href'] for x in resp.pyquery('.biglist.empty a:not(.fake)')] == [
|
||||
f'form-title/{x}/' for x in reversed(formdata_ids)
|
||||
resp = app.get('/backoffice/submission/pending')
|
||||
assert [x.attrib['data-link'] for x in resp.pyquery('tbody tr')] == [
|
||||
f'form-title/{x}/' for x in formdata_ids[:20]
|
||||
]
|
||||
|
||||
formdata.receipt_time = None # check a missing receipt_time is ok
|
||||
formdata.store()
|
||||
resp = app.get('/backoffice/submission/')
|
||||
assert [x.attrib['href'] for x in resp.pyquery('.biglist.empty a:not(.fake)')] == [
|
||||
f'form-title/{x}/' for x in reversed(formdata_ids)
|
||||
new_order = [formdata.id] + [x for x in formdata_ids if x != formdata.id]
|
||||
resp = app.get('/backoffice/submission/pending')
|
||||
assert [x.attrib['data-link'] for x in resp.pyquery('tbody tr')] == [
|
||||
f'form-title/{x}/' for x in new_order[:20]
|
||||
]
|
||||
assert 'unknown date' in resp.pyquery('li.smallitem:first').text()
|
||||
|
||||
resp = resp.click('<!--Next Page-->')
|
||||
assert [x.attrib['data-link'] for x in resp.pyquery('tbody tr')] == [
|
||||
f'form-title/{x}/' for x in new_order[20:]
|
||||
]
|
||||
|
||||
# check ajax call result
|
||||
resp = app.get('/backoffice/submission/pending?ajax=true')
|
||||
assert 'appbar' not in resp.text
|
||||
assert '<table' in resp.text
|
||||
assert 'page-links' in resp.text
|
||||
|
||||
|
||||
def test_backoffice_submission_prefill_user(pub):
|
||||
|
@ -1110,7 +1313,7 @@ def test_backoffice_submission_multiple_page_restore_on_validation(pub):
|
|||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
# restore draft
|
||||
resp = app.get('/backoffice/submission/')
|
||||
resp = app.get('/backoffice/submission/pending')
|
||||
resp = resp.click(href='form-title/%s' % formdata.id)
|
||||
resp = resp.follow()
|
||||
assert 'Check values then click submit.' in resp.text
|
||||
|
@ -1855,7 +2058,7 @@ def test_backoffice_submission_sidebar_lateral_block(pub):
|
|||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = []
|
||||
formdef.fields = [fields.StringField(id='1', label='1st field', varname='foo')]
|
||||
formdef.backoffice_submission_roles = user.roles[:]
|
||||
formdef.store()
|
||||
|
||||
|
@ -1866,13 +2069,27 @@ def test_backoffice_submission_sidebar_lateral_block(pub):
|
|||
formdef.submission_lateral_template = 'foo bar blah'
|
||||
formdef.store()
|
||||
|
||||
resp = app.get('/backoffice/submission/form-title/')
|
||||
assert '/lateral-block' in resp.text
|
||||
def get_lateral_block_url(resp):
|
||||
return app.get(re.findall('data-async-url="(.*/lateral-block.*?)"', resp.text)[0])
|
||||
|
||||
lateral_block_url = re.findall('data-async-url="(.*/lateral-block)"', resp.text)[0]
|
||||
partial_resp = app.get(lateral_block_url)
|
||||
resp = app.get('/backoffice/submission/form-title/')
|
||||
partial_resp = get_lateral_block_url(resp)
|
||||
assert partial_resp.text == '<div class="lateral-block">foo bar blah</div>'
|
||||
|
||||
# form in lateral template
|
||||
formdef.submission_lateral_template = (
|
||||
'foo {{ form_status }} - {{ form_submission_agent_name }} - x{{ form_var_foo }}y'
|
||||
)
|
||||
formdef.store()
|
||||
resp = app.get('/backoffice/submission/form-title/')
|
||||
partial_resp = get_lateral_block_url(resp)
|
||||
assert partial_resp.text == '<div class="lateral-block">foo Draft - admin - xNoney</div>'
|
||||
|
||||
resp.form['f1'] = 'blah'
|
||||
resp = resp.form.submit('submit') # -> validation page
|
||||
partial_resp = get_lateral_block_url(resp)
|
||||
assert partial_resp.text == '<div class="lateral-block">foo Draft - admin - xblahy</div>'
|
||||
|
||||
# webservice in lateral template
|
||||
NamedWsCall.wipe()
|
||||
wscall = NamedWsCall()
|
||||
|
@ -1884,7 +2101,8 @@ def test_backoffice_submission_sidebar_lateral_block(pub):
|
|||
rsps.get('http://remote.example.net/json', json={'foo': 'bar'})
|
||||
formdef.submission_lateral_template = 'XX{{webservice.hello_world.foo}}XX'
|
||||
formdef.store()
|
||||
partial_resp = app.get(lateral_block_url)
|
||||
resp = app.get('/backoffice/submission/form-title/')
|
||||
partial_resp = get_lateral_block_url(resp)
|
||||
assert partial_resp.text == '<div class="lateral-block">XXbarXX</div>'
|
||||
|
||||
# error in lateral template
|
||||
|
@ -1892,7 +2110,8 @@ def test_backoffice_submission_sidebar_lateral_block(pub):
|
|||
formdef.store()
|
||||
|
||||
pub.loggederror_class.wipe()
|
||||
partial_resp = app.get(lateral_block_url)
|
||||
resp = app.get('/backoffice/submission/form-title/')
|
||||
partial_resp = get_lateral_block_url(resp)
|
||||
assert partial_resp.text == ''
|
||||
assert pub.loggederror_class.count() == 1
|
||||
assert (
|
||||
|
@ -1900,6 +2119,19 @@ def test_backoffice_submission_sidebar_lateral_block(pub):
|
|||
== "Could not render submission lateral template ('datetime.date' object is not iterable)"
|
||||
)
|
||||
|
||||
formdef.submission_lateral_template = 'XX{{ "a"|add:bar }}XX'
|
||||
formdef.store()
|
||||
|
||||
pub.loggederror_class.wipe()
|
||||
resp = app.get('/backoffice/submission/form-title/')
|
||||
partial_resp = get_lateral_block_url(resp)
|
||||
assert partial_resp.text == ''
|
||||
assert pub.loggederror_class.count() == 1
|
||||
assert (
|
||||
pub.loggederror_class.select()[0].summary
|
||||
== "Could not render submission lateral template (missing variable \"bar\" in template)"
|
||||
)
|
||||
|
||||
|
||||
def test_backoffice_submission_computed_field(pub):
|
||||
user = create_user(pub)
|
||||
|
@ -2014,3 +2246,50 @@ def test_backoffice_submission_no_roles(pub):
|
|||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.data == {'1': 'xxx'}
|
||||
|
||||
|
||||
def test_backoffice_submission_then_front(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
front_user = pub.user_class()
|
||||
front_user.name = 'front user'
|
||||
front_user.email = 'test@invalid'
|
||||
front_user.store()
|
||||
account = PasswordAccount(id='front')
|
||||
account.set_password('front')
|
||||
account.user_id = front_user.id
|
||||
account.store()
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = [
|
||||
fields.PageField(id='0', label='1st page'),
|
||||
fields.StringField(id='1', label='Field on 1st page'),
|
||||
fields.PageField(id='2', label='2nd page'),
|
||||
fields.StringField(id='3', label='Field on 2nd page'),
|
||||
]
|
||||
formdef.backoffice_submission_roles = user.roles[:]
|
||||
formdef.workflow_roles = {'_receiver': 1}
|
||||
formdef.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/submission/')
|
||||
resp = resp.click(formdef.name)
|
||||
|
||||
resp.form['user_id'] = str(front_user.id) # happens via javascript
|
||||
resp.form['submission_channel'] = 'phone'
|
||||
resp.form['f1'] = 'test submission'
|
||||
resp = resp.form.submit('submit') # -> 2nd page
|
||||
resp.form['f3'] = 'baz'
|
||||
resp = resp.form.submit('submit') # -> validation page
|
||||
resp = resp.form.submit('submit') # final submit
|
||||
|
||||
formdata = formdef.data_class().get(resp.location.split('/')[-2])
|
||||
resp = login(get_app(pub), username='front', password='front').get(formdata.get_url())
|
||||
assert (
|
||||
resp.pyquery('.text-form-recorded').text()
|
||||
== f'The form has been recorded on {formdata.receipt_time.strftime("%Y-%m-%d %H:%M")} '
|
||||
f'with the number {formdata.get_display_id()}. It has been submitted for you by '
|
||||
f'admin after a phone call.'
|
||||
)
|
||||
|
|
|
@ -118,7 +118,8 @@ def test_workflow_inspect_page(pub):
|
|||
|
||||
resp = app.get('/backoffice/workflows/%s/inspect' % workflow.id)
|
||||
assert (
|
||||
'<span class="parameter">Model:</span> '
|
||||
'<span class="parameter">Model:</span> File</li>'
|
||||
'<li class="parameter-model_file">'
|
||||
'<a href="status/st3/items/_export_to/?file=model_file">test.odt</a></li>'
|
||||
) in resp.text
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import time
|
|||
import urllib.parse
|
||||
import xml.etree.ElementTree as ET
|
||||
import zipfile
|
||||
import zoneinfo
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
@ -1282,7 +1283,7 @@ def test_form_multi_page_page_name_as_title(pub):
|
|||
next_page = next_page.forms[0].submit('submit')
|
||||
assert_current_page(next_page, 'Validating')
|
||||
assert 'Check values then click submit.' in next_page.text
|
||||
assert next_page.text.count('1st page') == 2 # in steps and in main body
|
||||
assert next_page.text.count('1st page') == 3 # in steps (twice) and in main body
|
||||
|
||||
# add a comment that will not be displayed and should therefore not be
|
||||
# considered.
|
||||
|
@ -1305,7 +1306,48 @@ def test_form_multi_page_page_name_as_title(pub):
|
|||
next_page = next_page.forms[0].submit('submit')
|
||||
assert_current_page(next_page, 'Validating')
|
||||
assert 'Check values then click submit.' in next_page.text
|
||||
assert next_page.text.count('1st page') == 2 # in steps and in main body
|
||||
assert next_page.text.count('1st page') == 3 # in steps (twice) and in main body
|
||||
|
||||
|
||||
def test_form_multi_page_go_back(pub):
|
||||
formdef = create_formdef()
|
||||
formdef.fields = [
|
||||
fields.PageField(id='0', label='1st page'),
|
||||
fields.StringField(id='1', label='string'),
|
||||
fields.PageField(id='2', label='2nd page'),
|
||||
fields.PageField(id='3', label='3rd page'),
|
||||
fields.StringField(id='4', label='string 2'),
|
||||
]
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
resp = get_app(pub).get('/test/')
|
||||
resp.forms[0]['f1'] = 'foo'
|
||||
resp = resp.forms[0].submit('submit') # -> 2nd page
|
||||
assert_current_page(resp, '2nd page')
|
||||
resp = resp.forms[0].submit('submit') # -> 3rd page
|
||||
assert_current_page(resp, '3rd page')
|
||||
resp.forms[0]['f4'] = 'foo'
|
||||
resp = resp.forms[0].submit('submit') # -> validation page
|
||||
assert_current_page(resp, 'Validating')
|
||||
|
||||
# go back to second page (javascript would set this)
|
||||
resp.forms[0]['previous-page-id'] = '2'
|
||||
resp = resp.forms[0].submit('previous')
|
||||
assert_current_page(resp, '2nd page')
|
||||
resp = resp.forms[0].submit('submit') # -> 3rd page
|
||||
|
||||
# go back to first page (javascript would set this)
|
||||
resp.forms[0]['previous-page-id'] = '0'
|
||||
resp = resp.forms[0].submit('previous')
|
||||
assert_current_page(resp, '1st page')
|
||||
resp = resp.forms[0].submit('submit') # -> 2nd page
|
||||
resp = resp.forms[0].submit('submit') # -> 3rd page
|
||||
|
||||
# go back to invalid page (javascript would not set this)
|
||||
resp.forms[0]['previous-page-id'] = '10'
|
||||
resp = resp.forms[0].submit('previous')
|
||||
assert_current_page(resp, '1st page') # fallback to first page
|
||||
|
||||
|
||||
def test_form_submit_with_user(pub, emails):
|
||||
|
@ -3366,7 +3408,16 @@ def test_logged_errors(pub):
|
|||
)
|
||||
)[0]
|
||||
assert error.occurences_count == 2
|
||||
assert error.expression == '2//0'
|
||||
assert error.context == {
|
||||
'stack': [
|
||||
{
|
||||
'condition': '2//0',
|
||||
'condition_type': 'python',
|
||||
'source_label': 'Automatic Jump',
|
||||
'source_url': 'http://example.net/backoffice/workflows/12/status/just_submitted/items/_jump/',
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
assert pub.loggederror_class.count([Equal('formdef_id', '34')]) == 1
|
||||
assert pub.loggederror_class.count([Equal('formdef_id', 'X')]) == 0
|
||||
|
@ -3970,6 +4021,24 @@ def test_email_actions(pub, emails):
|
|||
assert html_payload.count('/actions/') == 6
|
||||
assert html_payload.count('button link start') == 4 # 2x2 buttons + 2x1 button
|
||||
|
||||
# check with missing label parameter
|
||||
pub.loggederror_class.wipe()
|
||||
workflow.possible_status[0].items[1].body = '''{% action_button "ok" %}'''
|
||||
workflow.store()
|
||||
emails.empty()
|
||||
formdef.data_class().wipe()
|
||||
app = login(get_app(pub), username='foo', password='foo')
|
||||
resp = app.get(formdef.get_url())
|
||||
resp = resp.form.submit('submit')
|
||||
resp = resp.form.submit('submit')
|
||||
assert not emails.get('New form2 (test email action)')
|
||||
assert pub.loggederror_class.count() == 1
|
||||
assert pub.loggederror_class.select()[0].summary == 'Error in body template, mail could not be generated'
|
||||
assert (
|
||||
pub.loggederror_class.select()[0].exception_message
|
||||
== '{% action_button %} requires a label parameter'
|
||||
)
|
||||
|
||||
|
||||
def test_card_email_actions(pub, emails):
|
||||
create_user(pub)
|
||||
|
@ -4059,6 +4128,26 @@ def test_email_temporary_form_button(pub, emails):
|
|||
resp = app.get(form_url).follow()
|
||||
assert 'The form has been recorded' in resp.text
|
||||
|
||||
# check with missing label parameter
|
||||
pub.loggederror_class.wipe()
|
||||
workflow.possible_status[0].items[1].body = 'Hello;\n{% temporary_access_button %}\nAdiós.'
|
||||
workflow.store()
|
||||
formdef.refresh_from_storage()
|
||||
emails.empty()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
formdata.store()
|
||||
assert not emails.get('New form')
|
||||
assert pub.loggederror_class.count() == 1
|
||||
assert pub.loggederror_class.select()[0].summary == 'Error in body template, mail could not be generated'
|
||||
assert (
|
||||
pub.loggederror_class.select()[0].exception_message
|
||||
== '{% temporary_action_button %} requires a label parameter'
|
||||
)
|
||||
|
||||
|
||||
def test_manager_public_access(pub):
|
||||
user, manager = create_user_and_admin(pub)
|
||||
|
@ -4936,7 +5025,10 @@ def test_create_formdata_locked_prefill_parent(create_formdata):
|
|||
|
||||
|
||||
def test_js_libraries(pub):
|
||||
create_formdef()
|
||||
formdef = create_formdef()
|
||||
formdef.enable_tracking_codes = True # will force gadjo.js -> jquery-ui
|
||||
formdef.store()
|
||||
|
||||
resp = get_app(pub).get('/test/', status=200)
|
||||
assert 'jquery.js' not in resp.text
|
||||
assert 'jquery.min.js' in resp.text
|
||||
|
@ -4947,6 +5039,8 @@ def test_js_libraries(pub):
|
|||
resp = get_app(pub).get('/test/', status=200)
|
||||
assert 'jquery.js' in resp.text
|
||||
assert 'jquery.min.js' not in resp.text
|
||||
assert 'jquery-ui.js' in resp.text
|
||||
assert 'jquery-ui.min.js' not in resp.text
|
||||
assert 'qommon.forms.js' in resp.text
|
||||
|
||||
pub.cfg['branding'] = {'included_js_libraries': ['jquery.js']}
|
||||
|
@ -4956,6 +5050,44 @@ def test_js_libraries(pub):
|
|||
assert 'jquery.min.js' not in resp.text
|
||||
assert 'qommon.forms.js' in resp.text
|
||||
|
||||
pub.cfg['branding'] = {'included_js_libraries': ['jquery.js', 'jquery-ui.js']}
|
||||
pub.write_cfg()
|
||||
resp = get_app(pub).get('/test/', status=200)
|
||||
assert 'jquery.js' not in resp.text
|
||||
assert 'jquery.min.js' not in resp.text
|
||||
assert 'qommon.forms.js' in resp.text
|
||||
|
||||
pub.cfg['branding'] = {'included_js_libraries': ['jquery.js']}
|
||||
pub.write_cfg()
|
||||
formdef.enable_tracking_codes = False # no popup, no jquery-ui (and no i18n.js)
|
||||
formdef.store()
|
||||
resp = get_app(pub).get('/test/', status=200)
|
||||
assert 'jquery-ui.js' not in resp.text
|
||||
assert 'jquery-ui.min.js' not in resp.text
|
||||
assert 'select2.js' not in resp.text
|
||||
assert 'i18n.js' not in resp.text
|
||||
|
||||
# add autocomplete field
|
||||
formdef.fields = [
|
||||
fields.ItemField(
|
||||
id='1',
|
||||
label='string',
|
||||
data_source={'type': 'jsonp', 'value': 'http://remote.example.net/jsonp'},
|
||||
),
|
||||
]
|
||||
formdef.store()
|
||||
resp = get_app(pub).get('/test/', status=200)
|
||||
assert 'select2.js' in resp.text
|
||||
assert 'select2.css' in resp.text
|
||||
assert 'i18n.js' in resp.text
|
||||
|
||||
pub.cfg['branding'] = {'included_js_libraries': ['jquery.js', 'select2.js']}
|
||||
pub.write_cfg()
|
||||
resp = get_app(pub).get('/test/', status=200)
|
||||
assert 'select2.js' not in resp.text
|
||||
assert 'select2.css' not in resp.text
|
||||
assert 'i18n.js' in resp.text
|
||||
|
||||
|
||||
def test_after_submit_location(pub):
|
||||
create_user(pub)
|
||||
|
@ -5607,7 +5739,7 @@ def test_form_edit_with_category(pub):
|
|||
assert 'f1' in resp.form.fields
|
||||
|
||||
|
||||
def test_form_edit_single_page(pub):
|
||||
def test_form_edit_single_or_partial_pages(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
workflow = Workflow(name='test')
|
||||
|
@ -5694,6 +5826,34 @@ def test_form_edit_single_page(pub):
|
|||
formdata.refresh_from_storage()
|
||||
assert formdata.data == {'2': 'a', '4': 'other change', '6': 'last change'}
|
||||
|
||||
# make page 2 hidden
|
||||
formdef.fields[2].condition = {'type': 'django', 'value': 'false'}
|
||||
formdef.store()
|
||||
|
||||
resp = app.get(formdata.get_url())
|
||||
resp = resp.form.submit('button_editable').follow()
|
||||
assert [x.text for x in resp.pyquery('#steps .wcs-step--label-text')] == ['3rd page']
|
||||
assert [x.text for x in resp.pyquery('.buttons button')] == ['Save Changes', 'Previous', 'Cancel']
|
||||
assert resp.pyquery('.buttons button.form-previous[hidden]')
|
||||
assert resp.pyquery('.buttons button.form-previous[disabled]')
|
||||
resp.form['f6'] = 'another last change'
|
||||
resp = resp.form.submit('submit')
|
||||
formdata.refresh_from_storage()
|
||||
assert formdata.data == {'2': 'a', '4': 'other change', '6': 'another last change'}
|
||||
|
||||
# also make page 3 hidden -> 404
|
||||
formdef.fields[4].condition = {'type': 'django', 'value': 'false'}
|
||||
formdef.store()
|
||||
resp = app.get(formdata.get_url())
|
||||
resp = resp.form.submit('button_editable').follow(status=404)
|
||||
|
||||
# check single page mode with hidden page (also 404)
|
||||
editable.operation_mode = 'partial'
|
||||
workflow.store()
|
||||
formdef.store()
|
||||
resp = app.get(formdata.get_url())
|
||||
resp = resp.form.submit('button_editable').follow(status=404)
|
||||
|
||||
|
||||
def test_form_edit_and_jump_on_submit(pub):
|
||||
wf = Workflow(name='edit and jump on submit')
|
||||
|
@ -5902,3 +6062,129 @@ def test_form_errors_summary(pub):
|
|||
resp = resp.forms[0].submit('submit')
|
||||
assert 'The following field has an error: testblock' in resp.pyquery('.errornotice').text()
|
||||
assert resp.pyquery('.error').text() == 'required field required field '
|
||||
|
||||
|
||||
def test_form_submit_no_csrf(pub):
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test'
|
||||
formdef.fields = [fields.StringField(id='0', label='string')]
|
||||
formdef.confirmation = False
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
create_user(pub)
|
||||
app = get_app(pub)
|
||||
login(app, username='foo', password='foo')
|
||||
resp = app.get(formdef.get_url())
|
||||
resp.form['f0'] = 'hello'
|
||||
# get expected data
|
||||
form_data = {x: y for x, y in resp.form.submit_fields('submit')}
|
||||
# remove token values
|
||||
form_data['_form_id'] = 'xxx'
|
||||
form_data['_ajax_form_token'] = 'xxx'
|
||||
form_data['magictoken'] = 'xxx'
|
||||
# simulate call from remote/attacker site (form token prevents this)
|
||||
resp = app.post(formdef.get_url(), params=form_data)
|
||||
assert 'The form you have submitted is invalid.' in resp.text
|
||||
|
||||
# with confirmation page
|
||||
formdef.confirmation = True
|
||||
formdef.store()
|
||||
resp = app.get(formdef.get_url())
|
||||
resp.form['f0'] = 'hello'
|
||||
resp = resp.form.submit('submit')
|
||||
# get expected data
|
||||
form_data = {x: y for x, y in resp.form.submit_fields('submit')}
|
||||
# remove token values
|
||||
form_data['_form_id'] = 'xxx'
|
||||
form_data['_ajax_form_token'] = 'xxx'
|
||||
form_data['magictoken'] = 'xxx'
|
||||
# simulate call from remote/attacker site (magictoken prevents this)
|
||||
resp = app.post(formdef.get_url(), params=form_data, status=302)
|
||||
assert resp.location == formdef.get_url()
|
||||
|
||||
# with multiple pages
|
||||
formdef.confirmation = False
|
||||
formdef.fields = [
|
||||
fields.PageField(id='1', label='page1'),
|
||||
fields.PageField(id='2', label='page2'),
|
||||
fields.StringField(id='3', label='string'),
|
||||
]
|
||||
formdef.store()
|
||||
resp = app.get(formdef.get_url())
|
||||
resp = resp.form.submit('submit')
|
||||
resp.form['f3'] = 'hello'
|
||||
# get expected data
|
||||
form_data = {x: y for x, y in resp.form.submit_fields('submit')}
|
||||
# remove token values
|
||||
form_data['_form_id'] = 'xxx'
|
||||
form_data['_ajax_form_token'] = 'xxx'
|
||||
form_data['magictoken'] = 'xxx'
|
||||
|
||||
# simulate call from remote/attacker site (magictokens prevents this)
|
||||
resp = app.post(formdef.get_url(), params=form_data, status=302)
|
||||
assert resp.location == formdef.get_url()
|
||||
|
||||
|
||||
def test_form_submit_no_csrf_suddenly_single_page(pub):
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test'
|
||||
formdef.fields = [
|
||||
fields.PageField(id='1', label='page1'),
|
||||
fields.ComputedField(id='2', label='computed', varname='plop', value_template='{{ "plop" }}'),
|
||||
fields.PageField(
|
||||
id='3', label='page2', condition={'type': 'django', 'value': 'form_var_plop != "plop"'}
|
||||
),
|
||||
]
|
||||
formdef.confirmation = False
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
create_user(pub)
|
||||
app = get_app(pub)
|
||||
login(app, username='foo', password='foo')
|
||||
resp = app.get(formdef.get_url())
|
||||
resp = resp.form.submit('submit').follow()
|
||||
assert formdef.data_class().select()[0].status == 'wf-new'
|
||||
|
||||
|
||||
def test_form_submit_timezone(pub):
|
||||
pub.load_site_options()
|
||||
if not pub.site_options.has_section('options'):
|
||||
pub.site_options.add_section('options')
|
||||
pub.site_options.set('options', 'timezone', 'Brazil/East')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
|
||||
formdef = create_formdef()
|
||||
formdef.data_class().wipe()
|
||||
app = get_app(pub)
|
||||
resp = app.get('/test/')
|
||||
resp = resp.forms[0].submit('submit') # -> validation
|
||||
resp = resp.forms[0].submit('submit').follow() # -> done
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.receipt_time.astimezone(zoneinfo.ZoneInfo('Brazil/East')).strftime('%H:%M') in resp.text
|
||||
assert (
|
||||
formdata.receipt_time.astimezone(zoneinfo.ZoneInfo('Europe/Paris')).strftime('%H:%M') not in resp.text
|
||||
)
|
||||
|
||||
pub.site_options.set('options', 'timezone', 'Europe/Paris')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
|
||||
resp = app.get(formdata.get_url())
|
||||
assert (
|
||||
formdata.receipt_time.astimezone(zoneinfo.ZoneInfo('Brazil/East')).strftime('%H:%M') not in resp.text
|
||||
)
|
||||
assert formdata.receipt_time.astimezone(zoneinfo.ZoneInfo('Europe/Paris')).strftime('%H:%M') in resp.text
|
||||
|
||||
# do not crash on invalid timezone
|
||||
pub.site_options.set('options', 'timezone', 'invalid')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
|
||||
resp = app.get(formdata.get_url())
|
||||
assert (
|
||||
formdata.receipt_time.astimezone(zoneinfo.ZoneInfo('Brazil/East')).strftime('%H:%M') not in resp.text
|
||||
)
|
||||
assert formdata.receipt_time.astimezone(zoneinfo.ZoneInfo('Europe/Paris')).strftime('%H:%M') in resp.text
|
||||
|
|
|
@ -715,6 +715,58 @@ def test_block_multi_string_modify_prefill(pub):
|
|||
assert resp.form['f3$element2$f123'].value == 'Bye World' # updated
|
||||
|
||||
|
||||
def test_block_string_prefill_and_items(pub):
|
||||
FormDef.wipe()
|
||||
BlockDef.wipe()
|
||||
|
||||
block = BlockDef()
|
||||
block.name = 'foobar'
|
||||
block.fields = [
|
||||
fields.StringField(
|
||||
id='123',
|
||||
required=True,
|
||||
label='Test',
|
||||
prefill={'type': 'string', 'value': '{{ form_var_foo }} World'},
|
||||
),
|
||||
fields.ItemsField(
|
||||
id='234',
|
||||
required=False,
|
||||
label='Items',
|
||||
items=['Pomme', 'Poire', 'Pêche', 'Abricot'],
|
||||
),
|
||||
]
|
||||
block.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = [
|
||||
fields.PageField(id='0', label='1st page'),
|
||||
fields.StringField(id='1', label='string', varname='foo'),
|
||||
fields.PageField(id='2', label='2nd page'),
|
||||
fields.BlockField(id='3', label='test', block_slug='foobar', max_items=5),
|
||||
]
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
app = get_app(pub)
|
||||
resp = app.get(formdef.get_url())
|
||||
resp.form['f1'] = 'Hello'
|
||||
resp = resp.form.submit('submit') # -> 2nd page
|
||||
assert resp.form['f3$element0$f123'].value == 'Hello World'
|
||||
resp = resp.form.submit('f3$add_element') # add second row
|
||||
assert resp.form['f3$element1$f123'].value == 'Hello World'
|
||||
resp = resp.form.submit('f3$add_element') # add third row
|
||||
assert resp.form['f3$element2$f123'].value == 'Hello World'
|
||||
resp = resp.form.submit('submit') # -> validation page
|
||||
resp = resp.form.submit('submit') # -> end page
|
||||
resp = resp.follow()
|
||||
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.data['3']['data'][0]['123'] == 'Hello World'
|
||||
assert formdata.data['3']['data'][1]['123'] == 'Hello World'
|
||||
assert formdata.data['3']['data'][2]['123'] == 'Hello World'
|
||||
|
||||
|
||||
def test_workflow_form_block_prefill(pub):
|
||||
FormDef.wipe()
|
||||
BlockDef.wipe()
|
||||
|
@ -2600,6 +2652,92 @@ def test_block_prefill_full_block_email(pub):
|
|||
}
|
||||
|
||||
|
||||
def test_block_prefill_full_block_card_item(pub):
|
||||
FormDef.wipe()
|
||||
BlockDef.wipe()
|
||||
CardDef.wipe()
|
||||
create_user(pub)
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'Test'
|
||||
carddef.fields = [
|
||||
fields.StringField(id='0', label='blah', varname='blah'),
|
||||
]
|
||||
carddef.digest_templates = {'default': '{{ form_var_blah|upper }}'}
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
|
||||
carddata1 = carddef.data_class()()
|
||||
carddata1.data = {'0': 'bar'}
|
||||
carddata1.just_created()
|
||||
carddata1.store()
|
||||
|
||||
block = BlockDef()
|
||||
block.name = 'foobar'
|
||||
block.fields = [
|
||||
fields.ItemField(
|
||||
id='123',
|
||||
required=False,
|
||||
hint='-----',
|
||||
label='Test',
|
||||
varname='plop',
|
||||
data_source={'type': 'carddef:test'},
|
||||
),
|
||||
]
|
||||
block.digest_template = '{{block_var_plop}}'
|
||||
block.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = [
|
||||
fields.PageField(id='0', label='1st page'),
|
||||
fields.PageField(id='2', label='2nd page'),
|
||||
fields.BlockField(
|
||||
id='1',
|
||||
label='test',
|
||||
block_slug='foobar',
|
||||
max_items=5,
|
||||
prefill={
|
||||
'type': 'string',
|
||||
'value': '{% block_value plop="1" %}',
|
||||
},
|
||||
),
|
||||
]
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
app = get_app(pub)
|
||||
login(app, username='foo', password='foo')
|
||||
resp = app.get(formdef.get_url())
|
||||
resp = resp.form.submit('submit') # -> page 2
|
||||
assert resp.form['f1$element0$f123'].value == '1'
|
||||
resp = resp.form.submit('submit') # validation
|
||||
resp = resp.form.submit('submit') # done
|
||||
assert formdef.data_class().select()[0].data == {
|
||||
'1': {
|
||||
'data': [
|
||||
{'123': '1', '123_display': 'BAR', '123_structured': {'blah': 'bar', 'id': 1, 'text': 'BAR'}}
|
||||
],
|
||||
'schema': {'123': 'item'},
|
||||
},
|
||||
'1_display': 'BAR',
|
||||
}
|
||||
|
||||
# prefill with unknown value
|
||||
pub.loggederror_class.wipe()
|
||||
formdef.fields[2].prefill['value'] = '{% block_value plop="123" %}'
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
resp = app.get(formdef.get_url())
|
||||
resp = resp.form.submit('submit') # -> page 2
|
||||
assert not resp.form['f1$element0$f123'].value
|
||||
assert pub.loggederror_class.count() == 1
|
||||
assert (
|
||||
pub.loggederror_class.select()[0].summary
|
||||
== 'invalid value when creating block: unknown card value (\'123\')'
|
||||
)
|
||||
|
||||
|
||||
def test_block_titles_and_empty_block_on_summary_page(pub, emails):
|
||||
FormDef.wipe()
|
||||
BlockDef.wipe()
|
||||
|
|
|
@ -2,6 +2,7 @@ import datetime
|
|||
import decimal
|
||||
|
||||
import pytest
|
||||
import responses
|
||||
from django.utils.timezone import make_aware
|
||||
from webtest import Upload
|
||||
|
||||
|
@ -303,12 +304,14 @@ def test_computed_field_recall_draft(pub):
|
|||
assert formdata.data == {'1': 'value'}
|
||||
|
||||
|
||||
def test_computed_field_complex_data(pub, http_requests):
|
||||
@pytest.mark.parametrize('http_method', ['get', 'post'])
|
||||
def test_computed_field_complex_data(pub, http_method):
|
||||
FormDef.wipe()
|
||||
NamedWsCall.wipe()
|
||||
|
||||
wscall = NamedWsCall()
|
||||
wscall.name = 'Hello world'
|
||||
wscall.request = {'url': 'http://remote.example.net/json'}
|
||||
wscall.request = {'url': 'http://remote.example.net/json', 'method': http_method.upper()}
|
||||
wscall.store()
|
||||
|
||||
formdef = FormDef()
|
||||
|
@ -326,14 +329,69 @@ def test_computed_field_complex_data(pub, http_requests):
|
|||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
resp = get_app(pub).get('/test/')
|
||||
assert 'XbarY' in resp.text
|
||||
resp = resp.forms[0].submit('submit') # -> validation
|
||||
resp = resp.forms[0].submit('submit').follow() # -> submit
|
||||
assert 'The form has been recorded' in resp.text
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.data['1'] == {'foo': 'bar'}
|
||||
# check with a dictionary as response
|
||||
with responses.RequestsMock() as rsps:
|
||||
getattr(rsps, http_method)('http://remote.example.net/json', json={'foo': 'bar'})
|
||||
resp = get_app(pub).get('/test/')
|
||||
assert 'XbarY' in resp.text
|
||||
resp = resp.forms[0].submit('submit') # -> validation
|
||||
resp = resp.forms[0].submit('submit').follow() # -> submit
|
||||
assert 'The form has been recorded' in resp.text
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.data['1'] == {'foo': 'bar'}
|
||||
|
||||
# check with a list as response
|
||||
formdef.data_class().wipe()
|
||||
with responses.RequestsMock() as rsps:
|
||||
formdef.fields[1].label = 'X{{form_var_computed_1_foo}}Y'
|
||||
formdef.store()
|
||||
getattr(rsps, http_method)('http://remote.example.net/json', json=[{'foo': 'xxx'}, {'foo': 'bar'}])
|
||||
resp = get_app(pub).get('/test/')
|
||||
assert 'XbarY' in resp.text
|
||||
resp = resp.forms[0].submit('submit') # -> validation
|
||||
resp = resp.forms[0].submit('submit').follow() # -> submit
|
||||
assert 'The form has been recorded' in resp.text
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.data['1'] == [{'foo': 'xxx'}, {'foo': 'bar'}]
|
||||
|
||||
# check with the computed field extracting a list from the response
|
||||
formdef.data_class().wipe()
|
||||
with responses.RequestsMock() as rsps:
|
||||
formdef.fields[0].value_template = '{{ webservice.hello_world|get:"data" }}'
|
||||
formdef.fields[1].label = 'X{{form_var_computed_1_foo}}Y'
|
||||
formdef.store()
|
||||
getattr(rsps, http_method)(
|
||||
'http://remote.example.net/json', json={'data': [{'foo': 'xxx'}, {'foo': 'bar'}]}
|
||||
)
|
||||
resp = get_app(pub).get('/test/')
|
||||
assert 'XbarY' in resp.text
|
||||
resp = resp.forms[0].submit('submit') # -> validation
|
||||
resp = resp.forms[0].submit('submit').follow() # -> submit
|
||||
assert 'The form has been recorded' in resp.text
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.data['1'] == [{'foo': 'xxx'}, {'foo': 'bar'}]
|
||||
|
||||
if http_method == 'post':
|
||||
# check with (complex) post data
|
||||
formdef.data_class().wipe()
|
||||
with responses.RequestsMock() as rsps:
|
||||
wscall.request['post_data'] = {'test': '{{ "test"|qrcode }}'}
|
||||
wscall.store()
|
||||
formdef.fields[0].value_template = '{{ webservice.hello_world|get:"data" }}'
|
||||
formdef.fields[1].label = 'X{{form_var_computed_1_foo}}Y'
|
||||
formdef.store()
|
||||
getattr(rsps, http_method)('http://remote.example.net/json', json={'data': {'1': {'foo': 'bar'}}})
|
||||
resp = get_app(pub).get('/test/')
|
||||
assert 'XbarY' in resp.text
|
||||
resp = resp.forms[0].submit('submit') # -> validation
|
||||
resp = resp.forms[0].submit('submit').follow() # -> submit
|
||||
assert 'The form has been recorded' in resp.text
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.data['1'] == {'1': {'foo': 'bar'}}
|
||||
|
||||
|
||||
def test_computed_field_decimal_data(pub, http_requests):
|
||||
|
|
|
@ -3,6 +3,7 @@ import time
|
|||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from django.utils.timezone import make_aware
|
||||
from webtest import Upload
|
||||
|
||||
from wcs import fields
|
||||
|
@ -386,7 +387,7 @@ def test_form_max_drafts(pub):
|
|||
# create another draft, not linked to user, to check it's not deleted
|
||||
another_draft = formdef.data_class()()
|
||||
another_draft.status = 'draft'
|
||||
another_draft.receipt_time = datetime.datetime(2023, 11, 23, 0, 0).timetuple()
|
||||
another_draft.receipt_time = make_aware(datetime.datetime(2023, 11, 23, 0, 0))
|
||||
another_draft.store()
|
||||
|
||||
drafts = []
|
||||
|
@ -394,7 +395,7 @@ def test_form_max_drafts(pub):
|
|||
draft = formdef.data_class()()
|
||||
draft.user_id = user.id
|
||||
draft.status = 'draft'
|
||||
draft.receipt_time = datetime.datetime(2023, 11, 23, 0, i).timetuple()
|
||||
draft.receipt_time = make_aware(datetime.datetime(2023, 11, 23, 0, i))
|
||||
draft.store()
|
||||
drafts.append(draft)
|
||||
|
||||
|
@ -413,6 +414,14 @@ def test_form_max_drafts(pub):
|
|||
|
||||
assert not formdef.data_class().has_key(drafts[0].id) # oldest draft was removed
|
||||
|
||||
formdef.drafts_max_per_user = '3'
|
||||
formdef.store()
|
||||
|
||||
resp = app.get('/test/')
|
||||
resp.form['f0'] = 'hello2'
|
||||
resp = resp.form.submit('submit')
|
||||
assert formdef.data_class().count([Equal('status', 'draft')]) == 4
|
||||
|
||||
|
||||
def test_form_draft_temporary_access_url(pub):
|
||||
FormDef.wipe()
|
||||
|
@ -549,3 +558,266 @@ def test_nothing_to_update_add_row(pub):
|
|||
sql_data_store.side_effect = NothingToUpdate
|
||||
resp = resp.form.submit('f2$add_element').follow()
|
||||
assert 'Technical error saving draft, please try again.' in resp.text
|
||||
|
||||
|
||||
def test_draft_store_page_id(pub):
|
||||
formdef = create_formdef()
|
||||
formdef.enable_tracking_codes = True
|
||||
formdef.fields = [
|
||||
fields.PageField(id='0', label='1st page'),
|
||||
fields.StringField(id='1', label='string 1'),
|
||||
fields.PageField(id='2', label='2nd page'),
|
||||
fields.StringField(id='3', label='string 2'),
|
||||
fields.PageField(id='4', label='3rd page'),
|
||||
fields.StringField(id='5', label='string 3'),
|
||||
]
|
||||
formdef.store()
|
||||
first_page_id = formdef.fields[0].id
|
||||
second_page_id = formdef.fields[2].id
|
||||
third_page_id = formdef.fields[4].id
|
||||
|
||||
formdef.data_class().wipe()
|
||||
|
||||
app = get_app(pub)
|
||||
resp = app.get('/test/')
|
||||
|
||||
resp.form['f1'] = 'test'
|
||||
# autosave
|
||||
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '0'
|
||||
assert formdata.page_id == first_page_id
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] is None
|
||||
assert formdata.data['5'] is None
|
||||
|
||||
resp = resp.form.submit('submit')
|
||||
# first page submitted, the draft in on the seconde page
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '1'
|
||||
assert formdata.page_id == second_page_id
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] is None
|
||||
assert formdata.data['5'] is None
|
||||
|
||||
resp.form['f3'] = 'foo'
|
||||
# autosave
|
||||
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '1'
|
||||
assert formdata.page_id == second_page_id
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] == 'foo'
|
||||
assert formdata.data['5'] is None
|
||||
|
||||
resp = resp.form.submit('submit')
|
||||
# second page submitted, the draft in on the third page
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '2'
|
||||
assert formdata.page_id == third_page_id
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] == 'foo'
|
||||
assert formdata.data['5'] is None
|
||||
|
||||
resp.form['f5'] = 'bar'
|
||||
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '2'
|
||||
assert formdata.page_id == third_page_id
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] == 'foo'
|
||||
assert formdata.data['5'] == 'bar'
|
||||
|
||||
resp = resp.form.submit('submit')
|
||||
# third page submitted, the draft in on the confirmation page
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '3'
|
||||
assert formdata.page_id == '_confirmation_page'
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] == 'foo'
|
||||
assert formdata.data['5'] == 'bar'
|
||||
|
||||
resp = resp.form.submit('previous')
|
||||
# back to third page
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '2'
|
||||
assert formdata.page_id == third_page_id
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] == 'foo'
|
||||
assert formdata.data['5'] == 'bar'
|
||||
|
||||
|
||||
def test_draft_store_page_id_no_confirmation(pub):
|
||||
formdef = create_formdef()
|
||||
formdef.enable_tracking_codes = True
|
||||
formdef.fields = [
|
||||
fields.PageField(id='0', label='1st page'),
|
||||
fields.StringField(id='1', label='string 1'),
|
||||
fields.PageField(id='2', label='2nd page'),
|
||||
fields.StringField(id='3', label='string 2'),
|
||||
fields.PageField(id='4', label='3rd page'),
|
||||
fields.StringField(id='5', label='string 3'),
|
||||
]
|
||||
formdef.confirmation = False
|
||||
formdef.store()
|
||||
first_page_id = formdef.fields[0].id
|
||||
second_page_id = formdef.fields[2].id
|
||||
third_page_id = formdef.fields[4].id
|
||||
|
||||
formdef.data_class().wipe()
|
||||
|
||||
app = get_app(pub)
|
||||
resp = app.get('/test/')
|
||||
|
||||
resp.form['f1'] = 'test'
|
||||
# autosave
|
||||
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '0'
|
||||
assert formdata.page_id == first_page_id
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] is None
|
||||
assert formdata.data['5'] is None
|
||||
|
||||
resp = resp.form.submit('submit')
|
||||
# first page submitted, the draft in on the seconde page
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '1'
|
||||
assert formdata.page_id == second_page_id
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] is None
|
||||
assert formdata.data['5'] is None
|
||||
|
||||
resp.form['f3'] = 'foo'
|
||||
# autosave
|
||||
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '1'
|
||||
assert formdata.page_id == second_page_id
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] == 'foo'
|
||||
assert formdata.data['5'] is None
|
||||
|
||||
resp = resp.form.submit('submit')
|
||||
# second page submitted, the draft in on the third page
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '2'
|
||||
assert formdata.page_id == third_page_id
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] == 'foo'
|
||||
assert formdata.data['5'] is None
|
||||
|
||||
resp.form['f5'] = 'bar'
|
||||
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '2'
|
||||
assert formdata.page_id == third_page_id
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['3'] == 'foo'
|
||||
assert formdata.data['5'] == 'bar'
|
||||
|
||||
resp = resp.form.submit('submit')
|
||||
# third page submitted, no more draft
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'wf-new'
|
||||
|
||||
|
||||
def test_draft_store_page_id_when_no_page(pub):
|
||||
formdef = create_formdef()
|
||||
formdef.enable_tracking_codes = True
|
||||
formdef.fields = [
|
||||
fields.StringField(id='1', label='string 1'),
|
||||
fields.StringField(id='2', label='string 2'),
|
||||
]
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
app = get_app(pub)
|
||||
resp = app.get('/test/')
|
||||
|
||||
resp.form['f1'] = 'test'
|
||||
resp.form['f2'] = 'bar'
|
||||
# autosave
|
||||
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '0'
|
||||
assert formdata.page_id == '_first_page'
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['2'] == 'bar'
|
||||
|
||||
resp = resp.form.submit('submit')
|
||||
# fields submitted, the draft in on the confirmation page
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '1'
|
||||
assert formdata.page_id == '_confirmation_page'
|
||||
assert formdata.data['1'] == 'test'
|
||||
|
||||
# back to first page
|
||||
resp = resp.form.submit('previous')
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '0'
|
||||
assert formdata.page_id == '_first_page'
|
||||
assert formdata.data['1'] == 'test'
|
||||
|
||||
|
||||
def test_draft_store_page_id_when_no_page_and_no_confirmation(pub):
|
||||
formdef = create_formdef()
|
||||
formdef.enable_tracking_codes = True
|
||||
formdef.fields = [
|
||||
fields.StringField(id='1', label='string 1'),
|
||||
fields.StringField(id='2', label='string 2'),
|
||||
]
|
||||
formdef.confirmation = False
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
app = get_app(pub)
|
||||
resp = app.get('/test/')
|
||||
|
||||
resp.form['f1'] = 'test'
|
||||
resp.form['f2'] = 'bar'
|
||||
# autosave
|
||||
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'draft'
|
||||
assert formdata.page_no == '0'
|
||||
assert formdata.page_id == '_first_page'
|
||||
assert formdata.data['1'] == 'test'
|
||||
assert formdata.data['2'] == 'bar'
|
||||
|
||||
resp = resp.form.submit('submit')
|
||||
# fields submitted, no more draft
|
||||
assert formdef.data_class().count() == 1
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.status == 'wf-new'
|
||||
|
|
|
@ -238,6 +238,42 @@ def test_form_file_field_image_submit(pub):
|
|||
assert '<img alt="" src="tempfile?' not in resp.text
|
||||
|
||||
|
||||
def test_form_file_field_html_submit(pub):
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test'
|
||||
formdef.fields = [fields.FileField(id='0', label='file')]
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
html_content = b'<html><body>hello</body></html>'
|
||||
upload = Upload('test.html', html_content, 'text/html')
|
||||
|
||||
app = get_app(pub)
|
||||
resp = app.get('/test/')
|
||||
resp.forms[0]['f0$file'] = upload
|
||||
resp = resp.forms[0].submit('submit')
|
||||
assert 'Check values then click submit.' in resp.text
|
||||
tempfile_id = resp.pyquery('.fileinfo .filename a').attr.href.split('=')[1]
|
||||
|
||||
resp_tempfile = app.get('/test/tempfile?t=%s' % tempfile_id)
|
||||
assert resp_tempfile.body == html_content
|
||||
|
||||
resp = resp.form.submit('submit').follow()
|
||||
assert resp.click('test.html').follow().content_type == 'text/html'
|
||||
assert resp.click('test.html').follow().body == html_content
|
||||
|
||||
# check it's also served raw from backoffice
|
||||
user = create_user(pub)
|
||||
user.is_admin = True
|
||||
user.store()
|
||||
app = get_app(pub)
|
||||
login(app, username='foo', password='foo')
|
||||
resp = app.get(formdef.data_class().select()[0].get_backoffice_url())
|
||||
assert resp.click('test.html').follow().content_type == 'text/html'
|
||||
assert resp.click('test.html').follow().body == html_content
|
||||
|
||||
|
||||
def test_form_file_field_submit_document_type(pub):
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
|
@ -537,3 +573,22 @@ def test_form_file_field_in_block_aria_description(pub):
|
|||
resp.pyquery.find('#' + resp.pyquery('[aria-describedby]').attr['aria-describedby']).text()
|
||||
== 'field label'
|
||||
)
|
||||
|
||||
|
||||
def test_file_download_url_on_wrong_field(pub):
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test'
|
||||
formdef.fields = [fields.StringField(id='1', label='str1')]
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
create_user(pub)
|
||||
app = get_app(pub)
|
||||
login(app, username='foo', password='foo')
|
||||
resp = app.get(formdef.get_url())
|
||||
resp.form['f1'] = 'test'
|
||||
resp = resp.form.submit('submit') # -> validation
|
||||
resp = resp.form.submit('submit').follow() # -> submit
|
||||
formdata = formdef.data_class().select()[0]
|
||||
app.get(formdata.get_url() + 'files/1/', status=404)
|
||||
|
|
|
@ -18,10 +18,14 @@ from wcs.data_sources import NamedDataSource
|
|||
from wcs.formdef import FormDef
|
||||
from wcs.qommon.form import UploadedFile
|
||||
from wcs.qommon.misc import ConnectionError
|
||||
from wcs.wf.create_formdata import Mapping
|
||||
from wcs.wf.export_to_model import transform_to_pdf
|
||||
from wcs.wf.form import WorkflowFormFieldsFormDef
|
||||
from wcs.workflows import ContentSnapshotPart, Workflow, WorkflowBackofficeFieldsFormDef
|
||||
from wcs.workflows import (
|
||||
AttachmentEvolutionPart,
|
||||
ContentSnapshotPart,
|
||||
Workflow,
|
||||
WorkflowBackofficeFieldsFormDef,
|
||||
)
|
||||
from wcs.wscalls import NamedWsCall
|
||||
|
||||
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
|
||||
|
@ -258,7 +262,7 @@ def test_formdata_attachment_download_to_backoffice_file_field_only(pub):
|
|||
attach = st1.add_action('addattachment', id='_attach')
|
||||
attach.by = ['_submitter']
|
||||
attach.backoffice_filefield_id = 'bo1'
|
||||
attach.attach_to_history = False # store only in backoffice field
|
||||
attach.attach_to_history = False # do not display in history
|
||||
wf.store()
|
||||
|
||||
assert attach.get_backoffice_filefield_options() == [('bo1', 'bo field 1', 'bo1')]
|
||||
|
@ -291,11 +295,56 @@ def test_formdata_attachment_download_to_backoffice_file_field_only(pub):
|
|||
assert bo1.content_type == 'text/plain'
|
||||
assert bo1.get_content() == b'foobar'
|
||||
|
||||
# but nothing in history
|
||||
# nothing displayed in history
|
||||
resp = resp.follow()
|
||||
assert 'resp.text' not in resp.text
|
||||
assert len(formdata.evolution) == 2
|
||||
assert len(formdata.evolution[0].parts) == 1
|
||||
assert isinstance(formdata.evolution[0].parts[0], ContentSnapshotPart)
|
||||
assert formdata.evolution[1].parts is None
|
||||
|
||||
# but attachment stored
|
||||
assert isinstance(formdata.evolution[1].parts[0], AttachmentEvolutionPart)
|
||||
|
||||
|
||||
def test_formdata_attachment_stored(pub):
|
||||
create_user(pub)
|
||||
wf = Workflow(name='status')
|
||||
st1 = wf.add_status('Status1', 'st1')
|
||||
attach = st1.add_action('addattachment', id='_attach')
|
||||
attach.by = ['_submitter']
|
||||
attach.backoffice_filefield_id = None # do not store as backoffice field
|
||||
attach.attach_to_history = False # do not display in history
|
||||
wf.store()
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test'
|
||||
formdef.workflow_id = wf.id
|
||||
formdef.fields = []
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
|
||||
resp = resp.forms[0].submit('submit')
|
||||
assert 'Check values then click submit.' in resp.text
|
||||
resp = resp.forms[0].submit('submit')
|
||||
assert resp.status_int == 302
|
||||
resp = resp.follow()
|
||||
assert 'The form has been recorded' in resp.text
|
||||
|
||||
resp.forms[0]['attachment_attach$file'] = Upload('test.txt', b'foobar', 'text/plain')
|
||||
resp = resp.forms[0].submit('button_attach')
|
||||
|
||||
# nothing displayed in history
|
||||
resp = resp.follow()
|
||||
assert 'resp.text' not in resp.text
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert len(formdata.evolution) == 2
|
||||
assert len(formdata.evolution[0].parts) == 1
|
||||
assert isinstance(formdata.evolution[0].parts[0], ContentSnapshotPart)
|
||||
|
||||
# but attachment stored
|
||||
assert isinstance(formdata.evolution[1].parts[0], AttachmentEvolutionPart)
|
||||
|
||||
|
||||
def test_formdata_attachment_file_options(pub):
|
||||
|
@ -1540,6 +1589,57 @@ def test_formdata_named_wscall_in_conditions(http_requests, pub):
|
|||
assert http_requests.count() == 1
|
||||
|
||||
|
||||
def test_formdata_error_with_wscall_in_conditions(http_requests, pub):
|
||||
create_user(pub)
|
||||
NamedWsCall.wipe()
|
||||
|
||||
wscall = NamedWsCall()
|
||||
wscall.name = 'Hello world'
|
||||
wscall.request = {'url': 'http://remote.example.net/404', 'method': 'GET'}
|
||||
wscall.record_on_errors = True
|
||||
wscall.store()
|
||||
assert wscall.slug == 'hello_world'
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test'
|
||||
formdef.fields = [
|
||||
fields.PageField(id='0', label='1st page'),
|
||||
fields.PageField(
|
||||
id='1',
|
||||
label='2nd page',
|
||||
condition={'type': 'python', 'value': 'webservice.hello_world["foo"] == "bar"'},
|
||||
),
|
||||
]
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
pub.loggederror_class.wipe()
|
||||
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
|
||||
assert '>1st page<' in resp.text
|
||||
assert '>2nd page<' in resp.text
|
||||
|
||||
# condition error and wscall error
|
||||
assert pub.loggederror_class.count() == 2
|
||||
wscall_error, condition_error = pub.loggederror_class.select(order_by='id')
|
||||
assert (
|
||||
wscall_error.context
|
||||
== condition_error.context
|
||||
== {
|
||||
'stack': [
|
||||
{
|
||||
'condition': 'webservice.hello_world["foo"] == "bar"',
|
||||
'condition_type': 'python',
|
||||
'source_label': 'Field: 2nd page',
|
||||
'source_url': 'http://example.net/backoffice/forms/1/fields/1/',
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
assert wscall_error.summary == '[WSCALL] 404 Not Found'
|
||||
assert condition_error.summary == 'Failed to evaluate condition'
|
||||
|
||||
|
||||
def test_formdata_named_wscall_in_comment(pub):
|
||||
create_user(pub)
|
||||
NamedWsCall.wipe()
|
||||
|
@ -1730,131 +1830,6 @@ def test_formdata_evolution_register_comment_to_with_attachment(pub):
|
|||
]
|
||||
|
||||
|
||||
def test_create_formdata_show_link_in_history(pub):
|
||||
FormDef.wipe()
|
||||
pub.tracking_code_class.wipe()
|
||||
|
||||
target_formdef = FormDef()
|
||||
target_formdef.name = 'target-form'
|
||||
target_formdef.fields = [
|
||||
fields.StringField(id='0', label='string', varname='foo_string'),
|
||||
]
|
||||
target_formdef.store()
|
||||
|
||||
wf = Workflow(name='create-formdata')
|
||||
wf.possible_status = Workflow.get_default_workflow().possible_status[:]
|
||||
create = wf.possible_status[1].add_action('create_formdata', id='_create', prepend=True)
|
||||
create.label = 'create a new linked form'
|
||||
create.varname = 'resubmitted'
|
||||
create.mappings = [
|
||||
Mapping(field_id='0', expression='="coincoin"'),
|
||||
]
|
||||
wf.store()
|
||||
|
||||
source_formdef = FormDef()
|
||||
source_formdef.name = 'source-form'
|
||||
source_formdef.fields = []
|
||||
source_formdef.workflow_id = wf.id
|
||||
source_formdef.enable_tracking_codes = True
|
||||
source_formdef.store()
|
||||
|
||||
create.formdef_slug = target_formdef.url_name
|
||||
create.attach_to_history = True
|
||||
wf.store()
|
||||
|
||||
source_formdef.data_class().wipe()
|
||||
target_formdef.data_class().wipe()
|
||||
|
||||
create_user(pub)
|
||||
app = login(get_app(pub), username='foo', password='foo')
|
||||
resp = app.get('/source-form/')
|
||||
resp = resp.forms[0].submit('submit')
|
||||
assert 'Check values then click submit.' in resp.text
|
||||
resp = resp.forms[0].submit('submit')
|
||||
assert resp.status_int == 302
|
||||
resp = resp.follow()
|
||||
assert 'The form has been recorded' in resp.text
|
||||
|
||||
formdata = source_formdef.data_class().select()[0]
|
||||
|
||||
# logged access: show link to created formdata
|
||||
resp = app.get('/source-form/%s/' % formdata.id)
|
||||
assert 'The form has been recorded on' in resp.text
|
||||
assert 'New form "target-form" created' in resp.text
|
||||
assert resp.pyquery('.wf-links a')
|
||||
|
||||
# anonymous access via tracking code: no link
|
||||
app = get_app(pub)
|
||||
resp = app.get('/code/%s/load' % formdata.tracking_code)
|
||||
resp = resp.follow()
|
||||
assert 'The form has been recorded on' in resp.text
|
||||
assert 'New form "target-form" created' not in resp.text
|
||||
assert not resp.pyquery('.wf-links a')
|
||||
|
||||
|
||||
def test_create_formdata_multiple(pub):
|
||||
FormDef.wipe()
|
||||
pub.tracking_code_class.wipe()
|
||||
|
||||
target_formdef = FormDef()
|
||||
target_formdef.name = 'target-form'
|
||||
target_formdef.fields = [
|
||||
fields.StringField(id='0', label='string', varname='foo_string'),
|
||||
]
|
||||
target_formdef.store()
|
||||
|
||||
wf = Workflow(name='create-formdata')
|
||||
wf.possible_status = Workflow.get_default_workflow().possible_status[:]
|
||||
global_action = wf.add_global_action('create formdata')
|
||||
trigger = global_action.triggers[0]
|
||||
trigger.roles = ['_submitter']
|
||||
create = global_action.add_action('create_formdata')
|
||||
create.label = 'create a new linked form'
|
||||
create.varname = 'resubmitted'
|
||||
create.mappings = [Mapping(field_id='0', expression='plop')]
|
||||
wf.store()
|
||||
|
||||
source_formdef = FormDef()
|
||||
source_formdef.name = 'source-form'
|
||||
source_formdef.fields = []
|
||||
source_formdef.workflow_id = wf.id
|
||||
source_formdef.enable_tracking_codes = True
|
||||
source_formdef.store()
|
||||
|
||||
create.formdef_slug = target_formdef.url_name
|
||||
wf.store()
|
||||
|
||||
source_formdef.data_class().wipe()
|
||||
target_formdef.data_class().wipe()
|
||||
|
||||
user = create_user(pub)
|
||||
|
||||
formdata = source_formdef.data_class()()
|
||||
formdata.user_id = user.id
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
formdata2 = source_formdef.data_class()()
|
||||
formdata2.user_id = user.id
|
||||
formdata2.just_created()
|
||||
formdata2.store()
|
||||
|
||||
app = login(get_app(pub), username='foo', password='foo')
|
||||
resp = app.get(formdata.get_url())
|
||||
|
||||
resp = resp.form.submit('button-action-1')
|
||||
assert target_formdef.data_class().count() == 1
|
||||
|
||||
resp = app.get(formdata.get_url())
|
||||
resp = resp.form.submit('button-action-1')
|
||||
assert target_formdef.data_class().count() == 2
|
||||
|
||||
# do it from another formdata (should not trigger recursive call detection)
|
||||
resp = app.get(formdata2.get_url())
|
||||
resp = resp.form.submit('button-action-1')
|
||||
assert target_formdef.data_class().count() == 3
|
||||
|
||||
|
||||
def test_include_authors_in_form_history(pub):
|
||||
user, admin = create_user_and_admin(pub)
|
||||
pub.role_class.wipe()
|
||||
|
|
|
@ -37,7 +37,7 @@ def pub():
|
|||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
|
||||
TranslatableMessage.do_table()
|
||||
TranslatableMessage.do_table() # update table with selected languages
|
||||
|
||||
return pub
|
||||
|
||||
|
@ -86,9 +86,18 @@ def test_i18n_form(pub, user, emails, http_requests):
|
|||
formdef = FormDef()
|
||||
formdef.name = 'test form'
|
||||
formdef.fields = [
|
||||
PageField(id='0', label='page field'),
|
||||
PageField(
|
||||
id='0',
|
||||
label='page field',
|
||||
post_conditions=[
|
||||
{
|
||||
'condition': {'type': 'django', 'value': 'form_var_text == "test"'},
|
||||
'error_message': 'page error message',
|
||||
},
|
||||
],
|
||||
),
|
||||
# label has a trailing white space to check for strip()
|
||||
StringField(id='1', label='text field ', hint='an hint text'),
|
||||
StringField(id='1', label='text field ', hint='an hint text', varname='text'),
|
||||
ItemField(
|
||||
id='2',
|
||||
label='list field',
|
||||
|
@ -126,6 +135,7 @@ def test_i18n_form(pub, user, emails, http_requests):
|
|||
('Notification Body', 'Contenu de notification'),
|
||||
('an hint text', 'un texte d’aide'),
|
||||
('a second hint text', 'un deuxième texte d’aide'),
|
||||
('page error message', 'message d’erreur de page'),
|
||||
):
|
||||
msg = TranslatableMessage()
|
||||
msg.string = en
|
||||
|
@ -152,6 +162,10 @@ def test_i18n_form(pub, user, emails, http_requests):
|
|||
assert resp.pyquery('#form_label_f1').text() == 'champ texte*'
|
||||
assert resp.pyquery('option:nth-child(3)').text() == 'deuxième'
|
||||
|
||||
resp.form['f1'] = 'xxx'
|
||||
resp = resp.form.submit('submit', headers={'Accept-Language': 'fr'})
|
||||
assert 'message d’erreur de page' in resp.pyquery('.global-errors').text()
|
||||
|
||||
resp.form['f1'] = 'test'
|
||||
resp.form['f2'] = 'second'
|
||||
resp.form['f3$element0'] = True
|
||||
|
|
|
@ -604,10 +604,10 @@ def test_item_field_from_custom_view_on_cards(pub):
|
|||
resp = app.get(formdef.data_class().select()[0].get_url())
|
||||
assert resp.pyquery('.field-type-item .value').text() == 'Yattr%sZ' % baz_id
|
||||
|
||||
# remove card (back to value stored at first)
|
||||
# remove card, the value is still displayed
|
||||
carddef.data_class().wipe()
|
||||
resp = app.get(formdef.data_class().select()[0].get_url())
|
||||
assert resp.pyquery('.field-type-item .value').text() == 'Xattr%sY' % baz_id
|
||||
assert resp.pyquery('.field-type-item .value').text() == 'Yattr%sZ' % baz_id
|
||||
|
||||
|
||||
def test_item_field_from_custom_view_on_cards_filter_status(pub):
|
||||
|
|
|
@ -777,6 +777,7 @@ def test_field_live_select_autocomplete_jsonvalue_prefill(pub, http_requests):
|
|||
assert resp.pyquery('[data-field-id="1"][data-live-source]')
|
||||
|
||||
|
||||
@pytest.mark.skipif('JOB_NAME' in os.environ, reason='jenkins python segfault')
|
||||
def test_field_live_select(pub, http_requests):
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
|
@ -1394,6 +1395,51 @@ def test_field_live_item_datasource_prefill_with_request_with_q(pub):
|
|||
)
|
||||
|
||||
|
||||
@responses.activate
|
||||
def test_field_live_item_datasource_prefill_with_invalid_data(pub):
|
||||
NamedDataSource.wipe()
|
||||
data_source = NamedDataSource(name='foobar')
|
||||
data_source.data_source = {
|
||||
'type': 'json',
|
||||
'value': 'http://remote.example.net/json',
|
||||
'qs_data': {'plop': '{{ form_var_foo }}'},
|
||||
}
|
||||
data_source.query_parameter = 'q'
|
||||
data_source.id_parameter = 'id'
|
||||
data_source.store()
|
||||
|
||||
data = {'data': [{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux', 'x': 'bye'}]}
|
||||
responses.get('http://remote.example.net/json', json=data)
|
||||
ds = {'type': 'foobar'}
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'Foo'
|
||||
formdef.fields = [
|
||||
fields.ItemField(
|
||||
id='1',
|
||||
label='item',
|
||||
data_source=ds,
|
||||
varname='foo',
|
||||
prefill={'type': 'string', 'value': '2'},
|
||||
),
|
||||
fields.ItemField(
|
||||
id='2',
|
||||
label='item',
|
||||
varname='item',
|
||||
prefill={'type': 'string', 'value': '{{ form_var_foo_structured_raw }}'}, # nope
|
||||
data_source=ds,
|
||||
),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
app = get_app(pub)
|
||||
resp = app.get('/foo/')
|
||||
app.post(
|
||||
'/foo/live?modified_field_id[]=init&prefilled_1=on&prefilled_2=on', params=resp.form.submit_fields()
|
||||
)
|
||||
|
||||
|
||||
def test_field_live_block_string_prefill(pub, http_requests):
|
||||
FormDef.wipe()
|
||||
BlockDef.wipe()
|
||||
|
@ -1732,6 +1778,102 @@ def test_dynamic_item_field_from_custom_view_on_cards(pub, field_type):
|
|||
assert logged_error.summary == '[DATASOURCE] Unknown custom view "as-data-source" for CardDef "items"'
|
||||
|
||||
|
||||
def test_dynamic_date_field_from_custom_view_on_cards(pub):
|
||||
pub.role_class.wipe()
|
||||
pub.custom_view_class.wipe()
|
||||
|
||||
user = create_user(pub)
|
||||
role = pub.role_class(name='xxx')
|
||||
role.store()
|
||||
user.roles = [role.id]
|
||||
user.is_admin = True
|
||||
user.store()
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test'
|
||||
formdef.fields = []
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
CardDef.wipe()
|
||||
carddef = CardDef()
|
||||
carddef.name = 'items'
|
||||
carddef.digest_templates = {'default': '{{form_var_attr}}'}
|
||||
carddef.workflow_roles = {'_editor': user.roles[0]}
|
||||
carddef.fields = [
|
||||
fields.StringField(id='1', label='string', varname='attr'),
|
||||
fields.DateField(id='2', label='date'),
|
||||
]
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
for i, value in enumerate(['foo', 'bar', 'baz']):
|
||||
carddata = carddef.data_class()()
|
||||
carddata.data = {
|
||||
'1': value,
|
||||
'2': datetime.date(2024, 1, 1 + i).timetuple(),
|
||||
}
|
||||
carddata.just_created()
|
||||
carddata.store()
|
||||
|
||||
# create custom view
|
||||
app = login(get_app(pub), username='foo', password='foo')
|
||||
|
||||
resp = app.get('/backoffice/data/items/')
|
||||
resp.forms['listing-settings']['filter-2'].checked = True
|
||||
resp.forms['listing-settings']['filter-status'].checked = True
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
|
||||
resp.forms['listing-settings']['filter'].value = 'recorded'
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
|
||||
resp.forms['save-custom-view']['title'] = 'as data source'
|
||||
resp.forms['save-custom-view']['visibility'] = 'datasource'
|
||||
resp = resp.forms['save-custom-view'].submit().follow()
|
||||
|
||||
# make sure <input type=date> is not used, so a template can be entered
|
||||
assert resp.pyquery('[name="filter-2-value"]')[0].attrib['type'] == 'text'
|
||||
resp.forms['listing-settings']['filter-2-value'] = '{{ form_var_blah }}'
|
||||
resp.forms['listing-settings']['filter-2-operator'].value = 'gte'
|
||||
|
||||
resp = resp.forms['listing-settings'].submit()
|
||||
assert resp.forms['listing-settings']['filter-2-value'].value == '{{ form_var_blah }}'
|
||||
assert resp.text.count('<tr') == 1 # thead only
|
||||
|
||||
# save custom view with filter
|
||||
resp = resp.forms['save-custom-view'].submit().follow()
|
||||
|
||||
custom_view = pub.custom_view_class.select()[0]
|
||||
|
||||
# use custom view as source
|
||||
ds = {'type': 'carddef:%s:%s' % (carddef.url_name, custom_view.slug)}
|
||||
formdef.fields = [
|
||||
fields.PageField(id='2', label='1st page'),
|
||||
fields.ItemField(
|
||||
id='0', label='item', varname='blah', items=['2023-01-02', '2024-01-02', '2025-01-02']
|
||||
),
|
||||
fields.ItemField(id='1', label='string', data_source=ds, display_disabled_items=True),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
resp = get_app(pub).get('/test/')
|
||||
assert resp.form['f1'].options == [('', False, '---')]
|
||||
resp.form['f0'] = '2024-01-02'
|
||||
live_resp = app.post('/test/live?modified_field_id[]=0', params=resp.form.submit_fields())
|
||||
assert live_resp.json['result']['1']['items'] == [
|
||||
{'attr': 'bar', 'id': 2, 'text': 'bar'},
|
||||
{'attr': 'baz', 'id': 3, 'text': 'baz'},
|
||||
]
|
||||
|
||||
resp.form['f0'] = '2023-01-02'
|
||||
live_resp = app.post('/test/live?modified_field_id[]=0', params=resp.form.submit_fields())
|
||||
assert len(live_resp.json['result']['1']['items']) == 3
|
||||
|
||||
resp.form['f0'] = '2025-01-02'
|
||||
live_resp = app.post('/test/live?modified_field_id[]=0', params=resp.form.submit_fields())
|
||||
assert len(live_resp.json['result']['1']['items']) == 0
|
||||
|
||||
|
||||
def test_dynamic_item_fields_from_custom_view_on_cards(pub):
|
||||
pub.role_class.wipe()
|
||||
pub.custom_view_class.wipe()
|
||||
|
|
|
@ -233,6 +233,7 @@ def test_form_map_field_default_position(pub):
|
|||
resp.form['f1'] = '169 rue du chateau, paris'
|
||||
resp = resp.form.submit('submit')
|
||||
assert resp.pyquery('.qommon-map').attr('data-def-lat') == '13'
|
||||
assert resp.pyquery('.qommon-map').attr('data-def-template')
|
||||
|
||||
formdef.fields[3].initial_position = 'template'
|
||||
formdef.fields[3].position_template = '{{ form_var_address }}'
|
||||
|
@ -244,3 +245,4 @@ def test_form_map_field_default_position(pub):
|
|||
rsps.get('https://nominatim.entrouvert.org/search', json=[{'lat': '48.8337085', 'lon': '2.3233693'}])
|
||||
resp = resp.form.submit('submit')
|
||||
assert resp.pyquery('.qommon-map').attr('data-def-lat') == '48.83370850'
|
||||
assert resp.pyquery('.qommon-map').attr('data-def-template')
|
||||
|
|
|
@ -10,8 +10,10 @@ from webtest import Upload
|
|||
from wcs import fields
|
||||
from wcs.admin.settings import UserFieldsFormDef
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.categories import Category
|
||||
from wcs.data_sources import NamedDataSource
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.wf.create_formdata import Mapping
|
||||
from wcs.workflows import Workflow
|
||||
|
||||
|
@ -401,6 +403,115 @@ def test_form_page_template_list_prefill(pub):
|
|||
assert 'invalid value selected' not in resp.text
|
||||
|
||||
|
||||
def test_form_page_template_list_prefill_by_text(pub):
|
||||
NamedDataSource.wipe()
|
||||
data_source = NamedDataSource(name='foobar')
|
||||
data_source.data_source = {
|
||||
'type': 'jsonvalue',
|
||||
'value': '[{"id": 1, "text": "foo"}, {"id": 2, "text": "bar"}]',
|
||||
}
|
||||
data_source.store()
|
||||
|
||||
formdef = create_formdef()
|
||||
formdef.data_class().wipe()
|
||||
formdef.fields = [
|
||||
fields.ItemField(
|
||||
id='1',
|
||||
label='item',
|
||||
varname='item',
|
||||
required=True,
|
||||
data_source={'type': data_source.slug},
|
||||
prefill={'type': 'string', 'value': 'bar'},
|
||||
)
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
resp = get_app(pub).get('/test/')
|
||||
assert resp.form['f1'].value == '2'
|
||||
assert 'invalid value selected' not in resp.text
|
||||
|
||||
# check with card data source
|
||||
CardDef.wipe()
|
||||
carddef = CardDef()
|
||||
carddef.name = 'Test'
|
||||
carddef.fields = [
|
||||
fields.StringField(id='0', label='blah', varname='blah'),
|
||||
]
|
||||
carddef.digest_templates = {'default': '{{ form_var_blah }}'}
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
|
||||
carddata1 = carddef.data_class()()
|
||||
carddata1.data = {'0': 'foo'}
|
||||
carddata1.just_created()
|
||||
carddata1.store()
|
||||
|
||||
carddata2 = carddef.data_class()()
|
||||
carddata2.data = {'0': 'bar'}
|
||||
carddata2.just_created()
|
||||
carddata2.store()
|
||||
|
||||
formdef.data_source = {'type': 'carddef:test'}
|
||||
formdef.store()
|
||||
|
||||
resp = get_app(pub).get('/test/')
|
||||
assert resp.form['f1'].value == str(carddata2.id)
|
||||
assert 'invalid value selected' not in resp.text
|
||||
|
||||
formdef.fields[0].prefill = {'type': 'string', 'value': '{{ %s }}' % carddata2.id}
|
||||
formdef.enable_tracking_codes = True
|
||||
formdef.store()
|
||||
app = get_app(pub)
|
||||
resp = app.get('/test/')
|
||||
assert resp.form['f1'].value == str(carddata2.id)
|
||||
assert 'invalid value selected' not in resp.text
|
||||
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('id_type', [int, str])
|
||||
def test_form_page_template_list_prefill_by_number(pub, id_type):
|
||||
NamedDataSource.wipe()
|
||||
data_source = NamedDataSource(name='foobar')
|
||||
data_source.data_source = {
|
||||
'type': 'jsonvalue',
|
||||
'value': json.dumps([{'id': id_type(1), 'text': 'foo'}, {'id': id_type(2), 'text': 'bar'}]),
|
||||
}
|
||||
data_source.store()
|
||||
|
||||
formdef = create_formdef()
|
||||
formdef.data_class().wipe()
|
||||
formdef.enable_tracking_codes = True
|
||||
formdef.fields = [
|
||||
fields.PageField(id='1', label='1st page'),
|
||||
fields.ItemField(
|
||||
id='2',
|
||||
label='item',
|
||||
varname='item',
|
||||
required=True,
|
||||
data_source={'type': data_source.slug},
|
||||
prefill={'type': 'string', 'value': '{{ 2 }}'},
|
||||
),
|
||||
fields.PageField(id='3', label='2nd page'),
|
||||
fields.ItemField(
|
||||
id='4',
|
||||
label='item',
|
||||
varname='item',
|
||||
required=True,
|
||||
data_source={'type': data_source.slug},
|
||||
prefill={'type': 'string', 'value': '{{ 3 }}'}, # invalid value
|
||||
),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
app = get_app(pub)
|
||||
resp = app.get('/test/')
|
||||
assert resp.form['f2'].value == '2'
|
||||
assert 'invalid value selected' not in resp.text
|
||||
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
|
||||
resp = resp.form.submit('submit')
|
||||
assert 'invalid value selected' in resp.text
|
||||
|
||||
|
||||
def test_form_page_query_string_list_prefill(pub):
|
||||
create_user(pub)
|
||||
formdef = create_formdef()
|
||||
|
@ -521,6 +632,109 @@ def test_form_page_item_with_variable_data_source_prefill(pub):
|
|||
assert not resp.pyquery('#form_error_f2').text()
|
||||
|
||||
|
||||
def test_form_page_item_with_card_with_custom_id_prefill(pub):
|
||||
create_user(pub)
|
||||
CardDef.wipe()
|
||||
FormDef.wipe()
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'Test'
|
||||
carddef.fields = [
|
||||
fields.StringField(id='0', label='blah', varname='blah'),
|
||||
]
|
||||
carddef.digest_templates = {'default': '{{ form_var_blah|upper }}'}
|
||||
carddef.id_template = '{{ form_var_blah }}'
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
|
||||
carddata1 = carddef.data_class()()
|
||||
carddata1.data = {'0': 'bar'}
|
||||
carddata1.just_created()
|
||||
carddata1.store()
|
||||
|
||||
carddata2 = carddef.data_class()()
|
||||
carddata2.data = {'0': 'foo'}
|
||||
carddata2.just_created()
|
||||
carddata2.store()
|
||||
|
||||
formdef = create_formdef()
|
||||
formdef.data_class().wipe()
|
||||
formdef.fields = [
|
||||
fields.ItemField(
|
||||
id='2',
|
||||
label='item',
|
||||
varname='item',
|
||||
required=False,
|
||||
data_source={'type': 'carddef:test'},
|
||||
prefill={'type': 'string', 'value': 'foo'},
|
||||
),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
resp = get_app(pub).get('/test/')
|
||||
assert [x.attrib['value'] for x in resp.pyquery('#form_f2 option')] == ['bar', 'foo']
|
||||
assert resp.form['f2'].value == 'foo'
|
||||
assert not resp.pyquery('#form_error_f2').text()
|
||||
|
||||
|
||||
def test_form_page_block_with_item_with_card_with_custom_id_prefill(pub):
|
||||
create_user(pub)
|
||||
CardDef.wipe()
|
||||
FormDef.wipe()
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'Test'
|
||||
carddef.fields = [
|
||||
fields.StringField(id='0', label='blah', varname='blah'),
|
||||
]
|
||||
carddef.digest_templates = {'default': 'card {{ form_var_blah }}'}
|
||||
carddef.id_template = '{{ form_var_blah }}'
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
|
||||
carddata1 = carddef.data_class()()
|
||||
carddata1.data = {'0': 'bar'}
|
||||
carddata1.just_created()
|
||||
carddata1.store()
|
||||
|
||||
carddata2 = carddef.data_class()()
|
||||
carddata2.data = {'0': 'foo'}
|
||||
carddata2.just_created()
|
||||
carddata2.store()
|
||||
|
||||
BlockDef.wipe()
|
||||
block = BlockDef()
|
||||
block.name = 'foobar'
|
||||
block.fields = [
|
||||
fields.ItemField(
|
||||
id='123',
|
||||
label='item',
|
||||
varname='item',
|
||||
required=False,
|
||||
data_source={'type': 'carddef:test'},
|
||||
),
|
||||
]
|
||||
block.store()
|
||||
|
||||
formdef = create_formdef()
|
||||
formdef.data_class().wipe()
|
||||
formdef.fields = [
|
||||
fields.BlockField(
|
||||
id='2',
|
||||
label='test',
|
||||
block_slug='foobar',
|
||||
varname='foobar',
|
||||
prefill={'type': 'string', 'value': '{% block_value item="foo" %}'},
|
||||
),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
resp = get_app(pub).get('/test/')
|
||||
assert [x.attrib['value'] for x in resp.pyquery('#form_f2__element0__f123 option')] == ['bar', 'foo']
|
||||
assert resp.form['f2$element0$f123'].value == 'foo'
|
||||
assert not resp.pyquery('.widget-with-error')
|
||||
|
||||
|
||||
def test_form_page_item_with_computed_field_variable_data_source_prefill(pub):
|
||||
create_user(pub)
|
||||
formdef = create_formdef()
|
||||
|
|
|
@ -11,6 +11,7 @@ from wcs.categories import Category
|
|||
from wcs.formdef import FormDef
|
||||
from wcs.qommon.template import Template
|
||||
from wcs.tracking_code import TrackingCode
|
||||
from wcs.wf.comment import WorkflowCommentPart
|
||||
|
||||
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
|
||||
from .test_all import assert_current_page, create_formdef, create_user, get_displayed_tracking_code
|
||||
|
@ -895,3 +896,30 @@ def test_temporary_access_url(pub, freezer):
|
|||
context = pub.substitutions.get_context_variables()
|
||||
tmpl = Template('{% temporary_access_url %}')
|
||||
assert tmpl.render(context) == ''
|
||||
|
||||
|
||||
def test_form_tracking_code_workflow_action(pub):
|
||||
formdef = create_formdef()
|
||||
formdef.fields = [fields.StringField(id='0', label='string')]
|
||||
formdef.enable_tracking_codes = True
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
resp = get_app(pub).get('/test/')
|
||||
resp.forms[0]['f0'] = 'foobar'
|
||||
resp = resp.forms[0].submit('submit') # -> validation
|
||||
resp = resp.forms[0].submit('submit') # -> done
|
||||
formdata = formdef.data_class().select()[0]
|
||||
|
||||
resp = get_app(pub).get(formdata.get_url(), status=302) # redirection to login
|
||||
|
||||
resp = get_app(pub).get('/')
|
||||
resp.forms[0]['code'] = formdata.tracking_code
|
||||
resp = resp.forms[0].submit().follow().follow()
|
||||
resp.forms['wf-actions']['comment'] = 'Test comment'
|
||||
resp = resp.forms['wf-actions'].submit('button_commentable')
|
||||
|
||||
# check action has been recorded as submitter
|
||||
formdata.refresh_from_storage()
|
||||
assert isinstance(formdata.evolution[-1].parts[0], WorkflowCommentPart)
|
||||
assert formdata.evolution[-1].who == '_submitter'
|
||||
|
|
|
@ -5,6 +5,7 @@ import xml.etree.ElementTree as ET
|
|||
import pytest
|
||||
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.carddata import UpdateRelationsAfterJob
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.categories import CardDefCategory
|
||||
from wcs.data_sources import NamedDataSource
|
||||
|
@ -1281,3 +1282,394 @@ def test_card_custom_id(pub, ids):
|
|||
'{{ cards|objects:"foo"|filter_by_identifier:"%s"|first|get:"form_identifier" }}' % ids[-1]
|
||||
)
|
||||
assert tmpl.render(context) == str(ids[-1])
|
||||
|
||||
|
||||
def test_card_custom_id_draft(pub):
|
||||
CardDef.wipe()
|
||||
carddef = CardDef()
|
||||
carddef.name = 'foo'
|
||||
carddef.fields = [
|
||||
StringField(id='1', label='Test', varname='foo'),
|
||||
]
|
||||
carddef.digest_templates = {'default': 'card {{ form_var_foo }}'}
|
||||
carddef.id_template = '{{ form_var_foo }}'
|
||||
carddef.store()
|
||||
|
||||
card = carddef.data_class()()
|
||||
card.data = {'1': 'id1'}
|
||||
card.status = 'draft'
|
||||
card.store()
|
||||
|
||||
card = carddef.data_class()()
|
||||
card.data = {'1': 'id1'}
|
||||
card.just_created()
|
||||
card.store()
|
||||
|
||||
assert carddef.data_class().get_by_id('id1').id == card.id
|
||||
|
||||
card.anonymise()
|
||||
with pytest.raises(KeyError):
|
||||
carddef.data_class().get_by_id('id1')
|
||||
|
||||
|
||||
def test_card_custom_id_format(pub):
|
||||
CardDef.wipe()
|
||||
carddef = CardDef()
|
||||
carddef.name = 'foo'
|
||||
carddef.store()
|
||||
|
||||
data_class = carddef.data_class()
|
||||
assert data_class.force_valid_id_characters('foobar') == 'foobar'
|
||||
assert data_class.force_valid_id_characters('Foobar') == 'Foobar'
|
||||
assert data_class.force_valid_id_characters(' Foobar') == 'Foobar'
|
||||
assert data_class.force_valid_id_characters(' Foo bar') == 'Foo-bar'
|
||||
assert data_class.force_valid_id_characters(' Fôô bar') == 'Foo-bar'
|
||||
assert data_class.force_valid_id_characters(' Fôô bar...') == 'Foo-bar'
|
||||
assert data_class.force_valid_id_characters('_Fôô bar-') == '_Foo-bar-'
|
||||
assert data_class.force_valid_id_characters('_Fôô bar-') == '_Foo-bar-'
|
||||
assert data_class.force_valid_id_characters('_Fôô bar☭-') == '_Foo-bar-'
|
||||
assert data_class.force_valid_id_characters('_Fôô bar❗') == '_Foo-bar'
|
||||
|
||||
|
||||
def test_card_update_related(pub):
|
||||
BlockDef.wipe()
|
||||
CardDef.wipe()
|
||||
FormDef.wipe()
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'foo'
|
||||
carddef.fields = [
|
||||
StringField(id='1', label='Test', varname='foo'),
|
||||
]
|
||||
carddef.digest_templates = {'default': '{{ form_var_foo }}'}
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
|
||||
carddata1 = carddef.data_class()()
|
||||
carddata1.data = {'1': 'card1'}
|
||||
carddata1.just_created()
|
||||
carddata1.store()
|
||||
|
||||
carddata2 = carddef.data_class()()
|
||||
carddata2.data = {'1': 'card2'}
|
||||
carddata2.just_created()
|
||||
carddata2.store()
|
||||
|
||||
# check update against item field
|
||||
formdef = FormDef()
|
||||
formdef.name = 'foo'
|
||||
formdef.fields = [
|
||||
ItemField(id='1', label='Test', data_source={'type': 'carddef:foo'}),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'1': '1'}
|
||||
formdata.data['1_display'] = formdef.fields[0].store_display_value(formdata.data, formdef.fields[0].id)
|
||||
assert formdata.data['1_display'] == 'card1'
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
pub.cleanup()
|
||||
carddef = carddef.get(carddef.id)
|
||||
carddata1 = carddef.data_class().get(carddata1.id)
|
||||
carddata1.data = {'1': 'card1-change1'}
|
||||
carddata1.store()
|
||||
|
||||
formdata.refresh_from_storage()
|
||||
assert formdata.data['1_display'] == 'card1-change1'
|
||||
|
||||
# check update against items field
|
||||
formdef = FormDef()
|
||||
formdef.name = 'foo'
|
||||
formdef.fields = [
|
||||
ItemsField(id='1', label='Test', data_source={'type': 'carddef:foo'}),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'1': ['1', '2']}
|
||||
formdata.data['1_display'] = formdef.fields[0].store_display_value(formdata.data, formdef.fields[0].id)
|
||||
assert formdata.data['1_display'] == 'card1-change1, card2'
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
pub.cleanup()
|
||||
carddef = carddef.get(carddef.id)
|
||||
carddata1 = carddef.data_class().get(carddata1.id)
|
||||
carddata1.data = {'1': 'card1-change2'}
|
||||
carddata1.store()
|
||||
|
||||
formdata.refresh_from_storage()
|
||||
assert formdata.data['1_display'] == 'card1-change2, card2'
|
||||
|
||||
# check update against block field
|
||||
blockdef = BlockDef()
|
||||
blockdef.name = 'foo'
|
||||
blockdef.fields = [
|
||||
ItemField(id='1', label='Test', varname='bar', data_source={'type': 'carddef:foo'}),
|
||||
]
|
||||
blockdef.digest_template = 'bloc:{{ block_var_bar }}'
|
||||
blockdef.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'foo2'
|
||||
formdef.fields = [
|
||||
BlockField(id='1', label='Test', block_slug=blockdef.slug),
|
||||
BlockField(id='2', label='Test2', block_slug=blockdef.slug), # left empty
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {
|
||||
'1': {
|
||||
'data': [
|
||||
{
|
||||
'1': '1',
|
||||
'1_display': 'card1-change2',
|
||||
},
|
||||
{
|
||||
'1': '2',
|
||||
'1_display': 'card2',
|
||||
},
|
||||
],
|
||||
'schema': {},
|
||||
}
|
||||
}
|
||||
|
||||
formdata.data['1_display'] = formdef.fields[0].store_display_value(formdata.data, formdef.fields[0].id)
|
||||
assert formdata.data['1_display'] == 'bloc:card1-change2, bloc:card2'
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
pub.cleanup()
|
||||
carddef = carddef.get(carddef.id)
|
||||
carddata1 = carddef.data_class().get(carddata1.id)
|
||||
carddata1.data = {'1': 'card1-change3'}
|
||||
carddata1.store()
|
||||
|
||||
formdata.refresh_from_storage()
|
||||
assert formdata.data['1']['data'][0]['1'] == '1'
|
||||
assert formdata.data['1']['data'][0]['1_display'] == 'card1-change3'
|
||||
assert formdata.data['1']['data'][1]['1'] == '2'
|
||||
assert formdata.data['1']['data'][1]['1_display'] == 'card2'
|
||||
assert formdata.data['1_display'] == 'bloc:card1-change3, bloc:card2'
|
||||
|
||||
|
||||
def test_card_update_related_with_custom_view(pub):
|
||||
CardDef.wipe()
|
||||
FormDef.wipe()
|
||||
pub.custom_view_class.wipe()
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'foo'
|
||||
carddef.fields = [
|
||||
StringField(id='1', label='Test', varname='foo'),
|
||||
]
|
||||
carddef.digest_templates = {
|
||||
'default': '{{ form_var_foo }}',
|
||||
'custom-view:view': 'view-{{ form_var_foo }}',
|
||||
}
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
|
||||
carddata1 = carddef.data_class()()
|
||||
carddata1.data = {'1': 'card1'}
|
||||
carddata1.just_created()
|
||||
carddata1.store()
|
||||
|
||||
carddata2 = carddef.data_class()()
|
||||
carddata2.data = {'1': 'card2'}
|
||||
carddata2.just_created()
|
||||
carddata2.store()
|
||||
|
||||
custom_view = pub.custom_view_class()
|
||||
custom_view.title = 'view'
|
||||
custom_view.formdef = carddef
|
||||
custom_view.columns = {'list': [{'id': 'id'}]}
|
||||
custom_view.filters = {}
|
||||
custom_view.visibility = 'datasource'
|
||||
custom_view.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'foo'
|
||||
formdef.fields = [
|
||||
ItemField(id='1', label='Test', data_source={'type': 'carddef:foo:view'}),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'1': '1'}
|
||||
formdata.data['1_display'] = formdef.fields[0].store_display_value(formdata.data, formdef.fields[0].id)
|
||||
assert formdata.data['1_display'] == 'view-card1'
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
pub.cleanup()
|
||||
carddef = carddef.get(carddef.id)
|
||||
carddata1 = carddef.data_class().get(carddata1.id)
|
||||
carddata1.data = {'1': 'card1-change1'}
|
||||
carddata1.store()
|
||||
|
||||
formdata.refresh_from_storage()
|
||||
assert formdata.data['1_display'] == 'view-card1-change1'
|
||||
|
||||
|
||||
def test_card_update_related_cascading(pub):
|
||||
BlockDef.wipe()
|
||||
CardDef.wipe()
|
||||
FormDef.wipe()
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'foo'
|
||||
carddef.fields = [
|
||||
StringField(id='1', label='Test', varname='foo'),
|
||||
]
|
||||
carddef.digest_templates = {'default': '{{ form_var_foo }}'}
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
|
||||
carddata1 = carddef.data_class()()
|
||||
carddata1.data = {'1': 'card1'}
|
||||
carddata1.just_created()
|
||||
carddata1.store()
|
||||
|
||||
carddef2 = CardDef()
|
||||
carddef2.name = 'bar'
|
||||
carddef2.fields = [
|
||||
ItemField(id='1', label='Test', varname='foo', data_source={'type': 'carddef:foo'}),
|
||||
]
|
||||
carddef2.digest_templates = {'default': 'bar-{{ form_var_foo }}'}
|
||||
carddef2.store()
|
||||
carddef2.data_class().wipe()
|
||||
|
||||
carddata2 = carddef2.data_class()()
|
||||
carddata2.data = {'1': '1'}
|
||||
carddata2.data['1_display'] = carddef2.fields[0].store_display_value(
|
||||
carddata2.data, carddef2.fields[0].id
|
||||
)
|
||||
carddata2.just_created()
|
||||
carddata2.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'foo'
|
||||
formdef.fields = [
|
||||
ItemField(id='1', label='Test', data_source={'type': 'carddef:bar'}),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'1': '1'}
|
||||
formdata.data['1_display'] = formdef.fields[0].store_display_value(formdata.data, formdef.fields[0].id)
|
||||
assert formdata.data['1_display'] == 'bar-card1'
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
pub.cleanup()
|
||||
carddef = carddef.get(carddef.id)
|
||||
carddata1 = carddef.data_class().get(carddata1.id)
|
||||
carddata1.data = {'1': 'card1-change1'}
|
||||
carddata1.store()
|
||||
|
||||
formdata.refresh_from_storage()
|
||||
assert formdata.data['1_display'] == 'bar-card1-change1'
|
||||
|
||||
|
||||
def test_card_update_related_cascading_loop(pub):
|
||||
BlockDef.wipe()
|
||||
CardDef.wipe()
|
||||
FormDef.wipe()
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'foo'
|
||||
carddef.fields = [
|
||||
StringField(id='1', label='Test', varname='foo'),
|
||||
ItemField(id='2', label='Test', varname='x', data_source={'type': 'carddef:bar'}),
|
||||
]
|
||||
carddef.digest_templates = {'default': '{{ form_var_foo }} {{ form_var_x }}'}
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
|
||||
carddef2 = CardDef()
|
||||
carddef2.name = 'bar'
|
||||
carddef2.fields = [
|
||||
StringField(id='1', label='Test', varname='foo'),
|
||||
ItemField(id='2', label='Test', varname='x', data_source={'type': 'carddef:foo'}),
|
||||
]
|
||||
carddef2.digest_templates = {'default': '{{ form_var_foo }} {{ form_var_x }}'}
|
||||
carddef2.store()
|
||||
carddef2.data_class().wipe()
|
||||
|
||||
carddata1 = carddef.data_class()()
|
||||
carddata1.data = {'1': 'card1'}
|
||||
carddata1.just_created()
|
||||
carddata1.store()
|
||||
|
||||
carddata2 = carddef2.data_class()()
|
||||
carddata2.data = {'1': 'card2', '2': '1'}
|
||||
carddata2.data['2_display'] = carddef2.fields[1].store_display_value(
|
||||
carddata2.data, carddef2.fields[1].id
|
||||
)
|
||||
assert carddata2.data['2_display'] == 'card1 None'
|
||||
carddata2.just_created()
|
||||
carddata2.store()
|
||||
|
||||
pub.cleanup()
|
||||
carddef = carddef.get(carddef.id)
|
||||
carddata1 = carddef.data_class().get(carddata1.id)
|
||||
carddata1.data['2'] = str(carddata2.id)
|
||||
carddata1.data['2_display'] = carddef.fields[1].store_display_value(carddata1.data, carddef.fields[1].id)
|
||||
carddata1.store()
|
||||
|
||||
# check it will have stopped once getting back to carddata2
|
||||
carddata2.refresh_from_storage()
|
||||
assert carddata2.data['2_display'] == 'card1 card2 card1 None'
|
||||
|
||||
|
||||
def test_card_update_related_deleted(pub):
|
||||
BlockDef.wipe()
|
||||
CardDef.wipe()
|
||||
FormDef.wipe()
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'foo'
|
||||
carddef.fields = [
|
||||
StringField(id='1', label='Test', varname='foo'),
|
||||
]
|
||||
carddef.digest_templates = {'default': 'card-{{ form_var_foo }}'}
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
|
||||
carddata1 = carddef.data_class()()
|
||||
carddata1.data = {'1': 'card1'}
|
||||
carddata1.just_created()
|
||||
carddata1.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'foo'
|
||||
formdef.fields = [
|
||||
ItemField(id='1', label='Test', data_source={'type': 'carddef:foo'}),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'1': '1'}
|
||||
formdata.data['1_display'] = formdef.fields[0].store_display_value(formdata.data, formdef.fields[0].id)
|
||||
assert formdata.data['1_display'] == 'card-card1'
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdef.remove_self()
|
||||
|
||||
pub.cleanup()
|
||||
carddef = carddef.get(carddef.id)
|
||||
carddata1 = carddef.data_class().get(carddata1.id)
|
||||
carddata1.data = {'1': 'card1-change1'}
|
||||
carddata1.store() # do not crash looking for related formdef that has been deleted
|
||||
|
||||
# check the job doesn't fail if the carddef or carddata have been removed
|
||||
job = UpdateRelationsAfterJob(carddata=carddata1)
|
||||
carddata1.remove_self()
|
||||
job.execute()
|
||||
|
||||
carddef.remove_self()
|
||||
job.execute()
|
||||
|
|
|
@ -7,10 +7,28 @@ import pytest
|
|||
from django.utils.encoding import force_bytes
|
||||
from quixote import cleanup
|
||||
|
||||
from wcs.categories import CardDefCategory, Category
|
||||
from wcs.categories import (
|
||||
BlockCategory,
|
||||
CardDefCategory,
|
||||
Category,
|
||||
CommentTemplateCategory,
|
||||
DataSourceCategory,
|
||||
MailTemplateCategory,
|
||||
WorkflowCategory,
|
||||
)
|
||||
|
||||
from .utilities import clean_temporary_pub, create_temporary_pub
|
||||
|
||||
category_classes = [
|
||||
Category,
|
||||
CardDefCategory,
|
||||
BlockCategory,
|
||||
WorkflowCategory,
|
||||
MailTemplateCategory,
|
||||
CommentTemplateCategory,
|
||||
DataSourceCategory,
|
||||
]
|
||||
|
||||
|
||||
def setup_module(module):
|
||||
cleanup()
|
||||
|
@ -24,7 +42,7 @@ def teardown_module(module):
|
|||
clean_temporary_pub()
|
||||
|
||||
|
||||
@pytest.mark.parametrize('category_class', [Category, CardDefCategory])
|
||||
@pytest.mark.parametrize('category_class', category_classes)
|
||||
def test_store(category_class):
|
||||
category_class.wipe()
|
||||
test = category_class()
|
||||
|
@ -37,7 +55,7 @@ def test_store(category_class):
|
|||
assert test.description == test2.description
|
||||
|
||||
|
||||
@pytest.mark.parametrize('category_class', [Category, CardDefCategory])
|
||||
@pytest.mark.parametrize('category_class', category_classes)
|
||||
def test_urlname(category_class):
|
||||
category_class.wipe()
|
||||
test = category_class()
|
||||
|
@ -48,7 +66,7 @@ def test_urlname(category_class):
|
|||
assert test.url_name == 'test'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('category_class', [Category, CardDefCategory])
|
||||
@pytest.mark.parametrize('category_class', category_classes)
|
||||
def test_duplicate_urlname(category_class):
|
||||
category_class.wipe()
|
||||
test = category_class()
|
||||
|
@ -64,7 +82,7 @@ def test_duplicate_urlname(category_class):
|
|||
assert test2.url_name == 'test-2'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('category_class', [Category, CardDefCategory])
|
||||
@pytest.mark.parametrize('category_class', category_classes)
|
||||
def test_name_giving_a_forbidden_slug(category_class):
|
||||
category_class.wipe()
|
||||
test = category_class()
|
||||
|
@ -74,7 +92,7 @@ def test_name_giving_a_forbidden_slug(category_class):
|
|||
assert test.url_name == 'cat-api'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('category_class', [Category, CardDefCategory])
|
||||
@pytest.mark.parametrize('category_class', category_classes)
|
||||
def test_sort_positions(category_class):
|
||||
category_class.wipe()
|
||||
|
||||
|
@ -94,7 +112,7 @@ def test_sort_positions(category_class):
|
|||
assert categories[-1].name in ('Test 8', 'Test 9')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('category_class', [Category, CardDefCategory])
|
||||
@pytest.mark.parametrize('category_class', category_classes)
|
||||
def test_xml_export(category_class):
|
||||
category_class.wipe()
|
||||
test = category_class()
|
||||
|
@ -108,7 +126,7 @@ def test_xml_export(category_class):
|
|||
assert b' id="1"' not in test.export_to_xml_string(include_id=False)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('category_class', [Category, CardDefCategory])
|
||||
@pytest.mark.parametrize('category_class', category_classes)
|
||||
def test_xml_import(category_class):
|
||||
category_class.wipe()
|
||||
test = category_class()
|
||||
|
@ -190,7 +208,7 @@ def test_load_old_python2_pickle():
|
|||
assert test2.description == 'Hello world'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('category_class', [Category, CardDefCategory])
|
||||
@pytest.mark.parametrize('category_class', category_classes)
|
||||
def test_get_by_urlname(category_class):
|
||||
category_class.wipe()
|
||||
test = category_class()
|
||||
|
@ -202,7 +220,7 @@ def test_get_by_urlname(category_class):
|
|||
assert test.id == test2.id
|
||||
|
||||
|
||||
@pytest.mark.parametrize('category_class', [Category, CardDefCategory])
|
||||
@pytest.mark.parametrize('category_class', category_classes)
|
||||
def test_has_urlname(category_class):
|
||||
category_class.wipe()
|
||||
test = category_class()
|
||||
|
@ -215,7 +233,7 @@ def test_has_urlname(category_class):
|
|||
assert not category_class.has_urlname('foobar')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('category_class', [Category, CardDefCategory])
|
||||
@pytest.mark.parametrize('category_class', category_classes)
|
||||
def test_remove_self(category_class):
|
||||
category_class.wipe()
|
||||
test = category_class()
|
||||
|
|
|
@ -150,7 +150,7 @@ def test_formdata_create_and_edit_and_bo_field(pub, user):
|
|||
# creation, bo field first action
|
||||
assert formdata.evolution[0].parts[1].formdef_type == 'formdef'
|
||||
assert formdata.evolution[0].parts[1].formdef_id == str(formdef.id)
|
||||
assert formdata.evolution[0].parts[1].old_data == {'1': 'bar'}
|
||||
assert formdata.evolution[0].parts[1].old_data == {'1': 'bar', 'bo1': None}
|
||||
assert formdata.evolution[0].parts[1].new_data == {'1': 'bar', 'bo1': 'bar'}
|
||||
dt2 = formdata.evolution[0].parts[1].datetime
|
||||
assert dt2 > dt1
|
||||
|
@ -1357,7 +1357,7 @@ def test_api_formdata_at(pub, user, access, role):
|
|||
evo.add_part(part)
|
||||
|
||||
evo = Evolution(formdata=formdata)
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
evo.status = formdata.status
|
||||
part = ContentSnapshotPart(formdata=formdata, old_data={'0': 'bar', 'bo1': 'foo'})
|
||||
part.new_data = {'0': 'baz', 'bo1': 'foo'}
|
||||
|
@ -1366,7 +1366,7 @@ def test_api_formdata_at(pub, user, access, role):
|
|||
formdata.evolution.append(evo)
|
||||
|
||||
evo = Evolution(formdata=formdata)
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
evo.status = formdata.status
|
||||
part = ContentSnapshotPart(formdata=formdata, old_data={'0': 'baz', 'bo1': 'foo'})
|
||||
part.new_data = {'0': 'foooo', '1': 'unknown', 'bo1': 'foo'}
|
||||
|
@ -1375,7 +1375,7 @@ def test_api_formdata_at(pub, user, access, role):
|
|||
formdata.evolution.append(evo)
|
||||
|
||||
evo = Evolution(formdata=formdata)
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
evo.status = formdata.status
|
||||
part = ContentSnapshotPart(formdata=formdata, old_data={'0': 'foooo', '1': 'unknown', 'bo1': 'foo'})
|
||||
part.new_data = {'0': 'fooo', 'bo1': 'foo'}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import collections
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import pickle
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import zipfile
|
||||
from unittest import mock
|
||||
|
@ -11,15 +10,12 @@ from unittest import mock
|
|||
import django
|
||||
import psycopg2
|
||||
import pytest
|
||||
import responses
|
||||
from django.core.management import CommandError, call_command
|
||||
|
||||
import wcs.qommon.ctl
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.ctl.delete_tenant import CmdDeleteTenant
|
||||
from wcs.ctl.management.commands.trigger_jumps import select_and_jump_formdata
|
||||
from wcs.ctl.rebuild_indexes import rebuild_vhost_indexes
|
||||
from wcs.ctl.wipe_data import CmdWipeData
|
||||
from wcs.fields import EmailField, ItemField, PageField, StringField
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.mail_templates import MailTemplate
|
||||
|
@ -56,16 +52,6 @@ def alt_tempdir():
|
|||
shutil.rmtree(alt_tempdir)
|
||||
|
||||
|
||||
def test_loading():
|
||||
ctl = wcs.qommon.ctl.Ctl(cmd_prefixes=['wcs.ctl'])
|
||||
ctl.load_all_commands(ignore_errors=False)
|
||||
# noqa pylint: disable=consider-iterating-dictionary
|
||||
assert 'shell' in ctl.get_commands().keys()
|
||||
# call all __init__() methods
|
||||
for cmd in ctl.get_commands().values():
|
||||
cmd()
|
||||
|
||||
|
||||
def test_collectstatic(pub, tmp_path):
|
||||
CmdCollectStatic.collectstatic(pub)
|
||||
assert os.path.exists(os.path.join(pub.app_dir, 'collectstatic', 'css', 'required.png'))
|
||||
|
@ -123,35 +109,49 @@ def test_wipe_formdata(pub):
|
|||
formdata_2.store()
|
||||
assert form_2.data_class().count() == 1
|
||||
|
||||
wipe_cmd = CmdWipeData()
|
||||
# no support for --all-tenants
|
||||
with pytest.raises(CommandError):
|
||||
call_command('wipe_data', '--all-tenants')
|
||||
|
||||
# check command options
|
||||
options, args = wipe_cmd.parse_args(['--all'])
|
||||
assert options.all
|
||||
# dry-run mode
|
||||
output = io.StringIO()
|
||||
call_command('wipe_data', '--domain=example.net', '--all', stdout=output)
|
||||
assert form_1.data_class().count() == 1
|
||||
assert form_2.data_class().count() == 1
|
||||
assert (
|
||||
output.getvalue()
|
||||
== '''SIMULATION MODE: no actual wiping will happen.
|
||||
(use --no-simulate after checking results)
|
||||
|
||||
options, args = wipe_cmd.parse_args([form_1.url_name, form_2.url_name])
|
||||
assert form_1.url_name in args
|
||||
assert form_2.url_name in args
|
||||
|
||||
sub_options_class = collections.namedtuple('Options', ['all'])
|
||||
sub_options = sub_options_class(False)
|
||||
example: 1
|
||||
example2: 1
|
||||
'''
|
||||
)
|
||||
|
||||
# test with no options
|
||||
wipe_cmd.wipe(pub, sub_options, [])
|
||||
call_command('wipe_data', '--domain=example.net', '--no-simulate')
|
||||
assert form_1.data_class().count() == 1
|
||||
assert form_2.data_class().count() == 1
|
||||
|
||||
# wipe one form formdatas
|
||||
wipe_cmd.wipe(pub, sub_options, [form_1.url_name])
|
||||
call_command('wipe_data', '--domain=example.net', '--no-simulate', '--forms=%s' % form_1.url_name)
|
||||
assert form_1.data_class().count() == 0
|
||||
assert form_2.data_class().count() == 1
|
||||
|
||||
# wipe all formdatas
|
||||
sub_options = sub_options_class(True)
|
||||
wipe_cmd.wipe(pub, sub_options, [])
|
||||
call_command('wipe_data', '--domain=example.net', '--no-simulate', '--all')
|
||||
assert form_1.data_class().count() == 0
|
||||
assert form_2.data_class().count() == 0
|
||||
|
||||
# exclude some forms
|
||||
formdata_1.store()
|
||||
formdata_2.store()
|
||||
call_command(
|
||||
'wipe_data', '--domain=example.net', '--no-simulate', '--all', '--exclude-forms=%s' % form_2.url_name
|
||||
)
|
||||
assert form_1.data_class().count() == 0
|
||||
assert form_2.data_class().count() == 1
|
||||
|
||||
|
||||
def test_trigger_jumps(pub):
|
||||
Workflow.wipe()
|
||||
|
@ -231,15 +231,11 @@ def test_trigger_jumps(pub):
|
|||
|
||||
def test_delete_tenant_with_sql(freezer):
|
||||
pub = create_temporary_pub()
|
||||
delete_cmd = CmdDeleteTenant()
|
||||
|
||||
assert os.path.isdir(pub.app_dir)
|
||||
|
||||
sub_options_class = collections.namedtuple('Options', ['force_drop'])
|
||||
sub_options = sub_options_class(False)
|
||||
|
||||
freezer.move_to('2018-12-01T00:00:00')
|
||||
delete_cmd.delete_tenant(pub, sub_options, [])
|
||||
call_command('delete_tenant', '--vhost=example.net')
|
||||
|
||||
assert not os.path.isdir(pub.app_dir)
|
||||
parent_dir = os.path.dirname(pub.app_dir)
|
||||
|
@ -259,8 +255,7 @@ def test_delete_tenant_with_sql(freezer):
|
|||
clean_temporary_pub()
|
||||
pub = create_temporary_pub()
|
||||
|
||||
sub_options = sub_options_class(True)
|
||||
delete_cmd.delete_tenant(pub, sub_options, [])
|
||||
call_command('delete_tenant', '--vhost=example.net', '--force-drop')
|
||||
|
||||
conn, cur = get_connection_and_cursor(new=True)
|
||||
|
||||
|
@ -286,13 +281,13 @@ def test_delete_tenant_with_sql(freezer):
|
|||
clean_temporary_pub()
|
||||
pub = create_temporary_pub()
|
||||
|
||||
cleanup_connection()
|
||||
sub_options = sub_options_class(True)
|
||||
pub.cfg['postgresql']['createdb-connection-params'] = {
|
||||
'user': pub.cfg['postgresql']['user'],
|
||||
'database': 'postgres',
|
||||
}
|
||||
delete_cmd.delete_tenant(pub, sub_options, [])
|
||||
pub.write_cfg()
|
||||
pub.cleanup()
|
||||
call_command('delete_tenant', '--vhost=example.net', '--force-drop')
|
||||
|
||||
connect_kwargs = {'dbname': 'postgres', 'user': pub.cfg['postgresql']['user']}
|
||||
pgconn = psycopg2.connect(**connect_kwargs)
|
||||
|
@ -312,12 +307,13 @@ def test_delete_tenant_with_sql(freezer):
|
|||
pub = create_temporary_pub()
|
||||
cleanup_connection()
|
||||
|
||||
sub_options = sub_options_class(False)
|
||||
pub.cfg['postgresql']['createdb-connection-params'] = {
|
||||
'user': pub.cfg['postgresql']['user'],
|
||||
'database': 'postgres',
|
||||
}
|
||||
delete_cmd.delete_tenant(pub, sub_options, [])
|
||||
pub.write_cfg()
|
||||
call_command('delete_tenant', '--vhost=example.net')
|
||||
cleanup_connection()
|
||||
|
||||
pgconn = psycopg2.connect(**connect_kwargs)
|
||||
pgconn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
|
||||
|
@ -350,39 +346,6 @@ def test_delete_tenant_with_sql(freezer):
|
|||
clean_temporary_pub()
|
||||
|
||||
|
||||
def test_delete_tenant_without_sql():
|
||||
pub = create_temporary_pub()
|
||||
delete_cmd = CmdDeleteTenant()
|
||||
|
||||
assert os.path.isdir(pub.app_dir)
|
||||
|
||||
sub_options_class = collections.namedtuple('Options', ['force_drop'])
|
||||
sub_options = sub_options_class(False)
|
||||
|
||||
delete_cmd.delete_tenant(pub, sub_options, [])
|
||||
|
||||
assert not os.path.isdir(pub.app_dir)
|
||||
parent_dir = os.path.dirname(pub.app_dir)
|
||||
if not [filename for filename in os.listdir(parent_dir) if 'removed' in filename]:
|
||||
assert False
|
||||
|
||||
clean_temporary_pub()
|
||||
|
||||
pub = create_temporary_pub()
|
||||
assert os.path.isdir(pub.app_dir)
|
||||
|
||||
sub_options = sub_options_class(True)
|
||||
|
||||
delete_cmd.delete_tenant(pub, sub_options, [])
|
||||
|
||||
assert not os.path.isdir(pub.app_dir)
|
||||
parent_dir = os.path.dirname(pub.app_dir)
|
||||
if [filename for filename in os.listdir(parent_dir) if 'removed' in filename]:
|
||||
assert False
|
||||
|
||||
clean_temporary_pub()
|
||||
|
||||
|
||||
def test_rebuild_indexes(pub):
|
||||
form = FormDef()
|
||||
form.name = 'example'
|
||||
|
@ -392,11 +355,11 @@ def test_rebuild_indexes(pub):
|
|||
|
||||
os.unlink(os.path.join(pub.app_dir, 'formdefs-url_name', 'example'))
|
||||
os.symlink('../formdefs/1', os.path.join(pub.app_dir, 'formdefs-url_name', 'XXX'))
|
||||
rebuild_vhost_indexes(pub, destroy=False)
|
||||
call_command('rebuild_indexes', '--all-tenants')
|
||||
assert 'example' in os.listdir(os.path.join(pub.app_dir, 'formdefs-url_name'))
|
||||
assert 'XXX' in os.listdir(os.path.join(pub.app_dir, 'formdefs-url_name'))
|
||||
|
||||
rebuild_vhost_indexes(pub, destroy=True)
|
||||
call_command('rebuild_indexes', '--all-tenants', '--destroy')
|
||||
assert os.listdir(os.path.join(pub.app_dir, 'formdefs-url_name')) == ['example']
|
||||
|
||||
|
||||
|
@ -415,10 +378,22 @@ open(os.path.join(get_publisher().app_dir, 'runscript.test'), 'w').close()
|
|||
)
|
||||
call_command('runscript', '--domain=example.net', os.path.join(pub.app_dir, 'test2.py'))
|
||||
assert os.path.exists(os.path.join(pub.app_dir, 'runscript.test'))
|
||||
|
||||
os.unlink(os.path.join(pub.app_dir, 'runscript.test'))
|
||||
call_command('runscript', '--all-tenants', os.path.join(pub.app_dir, 'test2.py'))
|
||||
assert os.path.exists(os.path.join(pub.app_dir, 'runscript.test'))
|
||||
|
||||
os.unlink(os.path.join(pub.app_dir, 'runscript.test'))
|
||||
call_command(
|
||||
'runscript', '--all-tenants', '--exclude-tenants=example.net', os.path.join(pub.app_dir, 'test2.py')
|
||||
)
|
||||
assert not os.path.exists(os.path.join(pub.app_dir, 'runscript.test'))
|
||||
|
||||
call_command(
|
||||
'runscript', '--all-tenants', '--exclude-tenants=example2.net', os.path.join(pub.app_dir, 'test2.py')
|
||||
)
|
||||
assert os.path.exists(os.path.join(pub.app_dir, 'runscript.test'))
|
||||
|
||||
|
||||
def test_import_site():
|
||||
with pytest.raises(CommandError):
|
||||
|
@ -530,26 +505,6 @@ def test_runjob(pub):
|
|||
call_command('runjob', '--domain=example.net', '--job-id=%s' % job.id, '--force-replay', '--raise')
|
||||
|
||||
|
||||
def test_ctl_print_help(capsys):
|
||||
ctl = wcs.qommon.ctl.Ctl(cmd_prefixes=['wcs.ctl'])
|
||||
with pytest.raises(SystemExit):
|
||||
ctl.print_help()
|
||||
captured = capsys.readouterr()
|
||||
assert 'runscript' in captured.out
|
||||
|
||||
|
||||
def test_ctl_no_command(capsys):
|
||||
ctl = wcs.qommon.ctl.Ctl(cmd_prefixes=['wcs.ctl'])
|
||||
old_argv, sys.argv = sys.argv, ['wcsctl']
|
||||
try:
|
||||
with pytest.raises(SystemExit):
|
||||
ctl.run(None)
|
||||
captured = capsys.readouterr()
|
||||
assert 'error: You must use a command' in captured.err
|
||||
finally:
|
||||
sys.argv = old_argv
|
||||
|
||||
|
||||
def test_dbshell(pub):
|
||||
with pytest.raises(CommandError):
|
||||
call_command('dbshell') # missing tenant name
|
||||
|
@ -1028,9 +983,11 @@ def test_configdb(pub):
|
|||
assert pub.cfg['postgresql']['database'] == database
|
||||
|
||||
|
||||
def test_replace_python(pub, alt_tempdir):
|
||||
@pytest.mark.parametrize('source_type', ['local-file', 'http'])
|
||||
def test_replace_python(pub, alt_tempdir, source_type):
|
||||
FormDef.wipe()
|
||||
Workflow.wipe()
|
||||
MailTemplate.wipe()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'Foo'
|
||||
|
@ -1041,6 +998,12 @@ def test_replace_python(pub, alt_tempdir):
|
|||
size='40',
|
||||
required=True,
|
||||
condition={'type': 'python', 'value': 'python condition'},
|
||||
post_conditions=[
|
||||
{
|
||||
'condition': {'type': 'python', 'value': 'python condition'},
|
||||
'error_message': 'You shall not pass.',
|
||||
},
|
||||
],
|
||||
),
|
||||
StringField(id='2', label='Foo', prefill={'type': 'formula', 'value': 'form_var_foo'}),
|
||||
StringField(id='3', label='Bar', prefill={'type': 'formula', 'value': 'form_var_bar'}),
|
||||
|
@ -1070,6 +1033,7 @@ def test_replace_python(pub, alt_tempdir):
|
|||
item = st1.add_action('sendmail')
|
||||
item.to = ['=form_var_foo']
|
||||
item.subject = '=form_var_foo'
|
||||
item.attachments = ['{{ form_var_xxx }}', 'getattr(form_attachments, "form_var_bar", None)']
|
||||
|
||||
item = st1.add_action('webservice_call')
|
||||
item.varname = 'xxx'
|
||||
|
@ -1086,6 +1050,11 @@ def test_replace_python(pub, alt_tempdir):
|
|||
|
||||
workflow.store()
|
||||
|
||||
mail_template = MailTemplate(name='test mail template')
|
||||
mail_template.subject = '=form_var_foo'
|
||||
mail_template.attachments = ['{{ form_var_xxx }}', 'getattr(form_attachments, "form_var_bar", None)']
|
||||
mail_template.store()
|
||||
|
||||
with open(os.path.join(alt_tempdir, 'replacements.json'), 'w') as fp:
|
||||
replacements = {
|
||||
'conditions': {
|
||||
|
@ -1094,19 +1063,33 @@ def test_replace_python(pub, alt_tempdir):
|
|||
'templates': {
|
||||
'form_var_foo': '{{ form_var_foo }}',
|
||||
'datetime.date(2023, 12, 27)': '2023-12-27',
|
||||
'getattr(form_attachments, "form_var_bar", None)': '{{ form_var_bar }}',
|
||||
},
|
||||
}
|
||||
json.dump(replacements, fp)
|
||||
|
||||
call_command(
|
||||
'replace_python',
|
||||
'--domain',
|
||||
'example.net',
|
||||
'--filename',
|
||||
os.path.join(alt_tempdir, 'replacements.json'),
|
||||
)
|
||||
if source_type == 'local-file':
|
||||
call_command(
|
||||
'replace_python',
|
||||
'--domain',
|
||||
'example.net',
|
||||
'--filename',
|
||||
os.path.join(alt_tempdir, 'replacements.json'),
|
||||
)
|
||||
else:
|
||||
source = 'https://test/replacements.json'
|
||||
with responses.RequestsMock() as rsps:
|
||||
rsps.get(source, json=replacements)
|
||||
call_command('replace_python', '--domain', 'example.net', '--url', source)
|
||||
|
||||
formdef.refresh_from_storage()
|
||||
assert formdef.fields[0].condition == {'type': 'django', 'value': 'django condition'}
|
||||
assert formdef.fields[0].post_conditions == [
|
||||
{
|
||||
'condition': {'type': 'django', 'value': 'django condition'},
|
||||
'error_message': 'You shall not pass.',
|
||||
},
|
||||
]
|
||||
assert formdef.fields[1].prefill == {'type': 'string', 'value': '{{ form_var_foo }}'}
|
||||
assert formdef.fields[2].prefill == {'type': 'formula', 'value': 'form_var_bar'} # no replacement
|
||||
workflow.refresh_from_storage()
|
||||
|
@ -1116,6 +1099,7 @@ def test_replace_python(pub, alt_tempdir):
|
|||
assert workflow.possible_status[0].items[3].mappings[0].expression == '{{ form_var_foo }}'
|
||||
assert workflow.possible_status[0].items[4].subject == '{{ form_var_foo }}'
|
||||
assert workflow.possible_status[0].items[4].to == ['{{ form_var_foo }}']
|
||||
assert workflow.possible_status[0].items[4].attachments == ['{{ form_var_xxx }}', '{{ form_var_bar }}']
|
||||
assert workflow.possible_status[0].items[5].post_data == {'str': 'abcd', 'expr': '{{ form_var_foo }}'}
|
||||
assert workflow.possible_status[0].items[5].qs_data == {
|
||||
'str': 'abcd',
|
||||
|
@ -1127,3 +1111,7 @@ def test_replace_python(pub, alt_tempdir):
|
|||
assert workflow.global_actions[0].triggers[1].anchor_template == '2023-12-27'
|
||||
assert workflow.global_actions[0].triggers[2].anchor == 'python'
|
||||
assert workflow.global_actions[0].triggers[2].anchor_expression == 'nope'
|
||||
|
||||
mail_template.refresh_from_storage()
|
||||
assert mail_template.subject == '{{ form_var_foo }}'
|
||||
assert mail_template.attachments == ['{{ form_var_xxx }}', '{{ form_var_bar }}']
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import xml.etree.ElementTree as ET
|
||||
|
||||
import pytest
|
||||
|
||||
from wcs import data_sources
|
||||
|
@ -57,11 +59,11 @@ def test_datasource_users(pub):
|
|||
|
||||
assert data_sources.get_items({'type': datasource.slug}) == [
|
||||
(
|
||||
'1',
|
||||
str(users[0].id),
|
||||
'John Doe 0',
|
||||
'1',
|
||||
str(users[0].id),
|
||||
{
|
||||
'id': 1,
|
||||
'id': users[0].id,
|
||||
'text': 'John Doe 0',
|
||||
'user_name_identifier_0': 'abc0',
|
||||
'user_nameid': 'abc0',
|
||||
|
@ -72,11 +74,11 @@ def test_datasource_users(pub):
|
|||
},
|
||||
),
|
||||
(
|
||||
'2',
|
||||
str(users[1].id),
|
||||
'John Doe 1',
|
||||
'2',
|
||||
str(users[1].id),
|
||||
{
|
||||
'id': 2,
|
||||
'id': users[1].id,
|
||||
'text': 'John Doe 1',
|
||||
'user_name_identifier_0': 'abc1',
|
||||
'user_nameid': 'abc1',
|
||||
|
@ -89,11 +91,11 @@ def test_datasource_users(pub):
|
|||
]
|
||||
assert data_sources.get_items(datasource.extended_data_source) == [
|
||||
(
|
||||
'1',
|
||||
str(users[0].id),
|
||||
'John Doe 0',
|
||||
'1',
|
||||
str(users[0].id),
|
||||
{
|
||||
'id': 1,
|
||||
'id': users[0].id,
|
||||
'text': 'John Doe 0',
|
||||
'user_name_identifier_0': 'abc0',
|
||||
'user_nameid': 'abc0',
|
||||
|
@ -104,11 +106,11 @@ def test_datasource_users(pub):
|
|||
},
|
||||
),
|
||||
(
|
||||
'2',
|
||||
str(users[1].id),
|
||||
'John Doe 1',
|
||||
'2',
|
||||
str(users[1].id),
|
||||
{
|
||||
'id': 2,
|
||||
'id': users[1].id,
|
||||
'text': 'John Doe 1',
|
||||
'user_name_identifier_0': 'abc1',
|
||||
'user_nameid': 'abc1',
|
||||
|
@ -121,7 +123,7 @@ def test_datasource_users(pub):
|
|||
]
|
||||
assert data_sources.get_structured_items({'type': datasource.slug}) == [
|
||||
{
|
||||
'id': 1,
|
||||
'id': users[0].id,
|
||||
'text': 'John Doe 0',
|
||||
'user_name_identifier_0': 'abc0',
|
||||
'user_nameid': 'abc0',
|
||||
|
@ -131,7 +133,7 @@ def test_datasource_users(pub):
|
|||
'user_email': None,
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'id': users[1].id,
|
||||
'text': 'John Doe 1',
|
||||
'user_name_identifier_0': 'abc1',
|
||||
'user_nameid': 'abc1',
|
||||
|
@ -143,7 +145,7 @@ def test_datasource_users(pub):
|
|||
]
|
||||
assert data_sources.get_structured_items(datasource.extended_data_source) == [
|
||||
{
|
||||
'id': 1,
|
||||
'id': users[0].id,
|
||||
'text': 'John Doe 0',
|
||||
'user_name_identifier_0': 'abc0',
|
||||
'user_nameid': 'abc0',
|
||||
|
@ -153,7 +155,7 @@ def test_datasource_users(pub):
|
|||
'user_email': None,
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'id': users[1].id,
|
||||
'text': 'John Doe 1',
|
||||
'user_name_identifier_0': 'abc1',
|
||||
'user_nameid': 'abc1',
|
||||
|
@ -169,7 +171,7 @@ def test_datasource_users(pub):
|
|||
datasource.store()
|
||||
assert data_sources.get_structured_items({'type': datasource.slug}) == [
|
||||
{
|
||||
'id': 1,
|
||||
'id': users[0].id,
|
||||
'text': 'John Doe 0',
|
||||
'user_name_identifier_0': 'abc0',
|
||||
'user_nameid': 'abc0',
|
||||
|
@ -179,7 +181,7 @@ def test_datasource_users(pub):
|
|||
'user_email': None,
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'id': users[1].id,
|
||||
'text': 'John Doe 1',
|
||||
'user_name_identifier_0': 'abc1',
|
||||
'user_nameid': 'abc1',
|
||||
|
@ -191,7 +193,7 @@ def test_datasource_users(pub):
|
|||
]
|
||||
assert data_sources.get_structured_items(datasource.extended_data_source) == [
|
||||
{
|
||||
'id': 1,
|
||||
'id': users[0].id,
|
||||
'text': 'John Doe 0',
|
||||
'user_name_identifier_0': 'abc0',
|
||||
'user_nameid': 'abc0',
|
||||
|
@ -201,7 +203,7 @@ def test_datasource_users(pub):
|
|||
'user_email': None,
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'id': users[1].id,
|
||||
'text': 'John Doe 1',
|
||||
'user_name_identifier_0': 'abc1',
|
||||
'user_nameid': 'abc1',
|
||||
|
@ -223,7 +225,7 @@ def test_datasource_users(pub):
|
|||
users[0].store()
|
||||
assert data_sources.get_structured_items({'type': datasource.slug}) == [
|
||||
{
|
||||
'id': 1,
|
||||
'id': users[0].id,
|
||||
'text': 'John Doe 0',
|
||||
'user_name_identifier_0': 'abc0',
|
||||
'user_nameid': 'abc0',
|
||||
|
@ -235,7 +237,7 @@ def test_datasource_users(pub):
|
|||
]
|
||||
assert data_sources.get_structured_items(datasource.extended_data_source) == [
|
||||
{
|
||||
'id': 1,
|
||||
'id': users[0].id,
|
||||
'text': 'John Doe 0',
|
||||
'user_name_identifier_0': 'abc0',
|
||||
'user_nameid': 'abc0',
|
||||
|
@ -254,7 +256,7 @@ def test_datasource_users(pub):
|
|||
datasource.store()
|
||||
assert data_sources.get_structured_items({'type': datasource.slug}) == [
|
||||
{
|
||||
'id': 1,
|
||||
'id': users[0].id,
|
||||
'text': 'John Doe 0',
|
||||
'user_name_identifier_0': 'abc0',
|
||||
'user_nameid': 'abc0',
|
||||
|
@ -266,7 +268,7 @@ def test_datasource_users(pub):
|
|||
]
|
||||
assert data_sources.get_structured_items(datasource.extended_data_source) == [
|
||||
{
|
||||
'id': 1,
|
||||
'id': users[0].id,
|
||||
'text': 'John Doe 0',
|
||||
'user_name_identifier_0': 'abc0',
|
||||
'user_nameid': 'abc0',
|
||||
|
@ -291,7 +293,7 @@ def test_datasource_users(pub):
|
|||
assert not datasource.include_disabled_users
|
||||
assert data_sources.get_structured_items({'type': datasource.slug}) == [
|
||||
{
|
||||
'id': 1,
|
||||
'id': users[0].id,
|
||||
'text': 'John Doe 0',
|
||||
'user_name_identifier_0': 'abc0',
|
||||
'user_nameid': 'abc0',
|
||||
|
@ -303,7 +305,7 @@ def test_datasource_users(pub):
|
|||
]
|
||||
assert data_sources.get_structured_items(datasource.extended_data_source) == [
|
||||
{
|
||||
'id': 1,
|
||||
'id': users[0].id,
|
||||
'text': 'John Doe 0',
|
||||
'user_name_identifier_0': 'abc0',
|
||||
'user_nameid': 'abc0',
|
||||
|
@ -319,7 +321,7 @@ def test_datasource_users(pub):
|
|||
datasource.store()
|
||||
assert data_sources.get_structured_items({'type': datasource.slug}) == [
|
||||
{
|
||||
'id': 1,
|
||||
'id': users[0].id,
|
||||
'text': 'John Doe 0',
|
||||
'user_name_identifier_0': 'abc0',
|
||||
'user_nameid': 'abc0',
|
||||
|
@ -329,7 +331,7 @@ def test_datasource_users(pub):
|
|||
'user_email': None,
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'id': users[1].id,
|
||||
'text': 'John Doe 1',
|
||||
'user_name_identifier_0': 'abc1',
|
||||
'user_nameid': 'abc1',
|
||||
|
@ -341,7 +343,7 @@ def test_datasource_users(pub):
|
|||
]
|
||||
assert data_sources.get_structured_items(datasource.extended_data_source) == [
|
||||
{
|
||||
'id': 1,
|
||||
'id': users[0].id,
|
||||
'text': 'John Doe 0',
|
||||
'user_name_identifier_0': 'abc0',
|
||||
'user_nameid': 'abc0',
|
||||
|
@ -351,7 +353,7 @@ def test_datasource_users(pub):
|
|||
'user_email': None,
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'id': users[1].id,
|
||||
'text': 'John Doe 1',
|
||||
'user_name_identifier_0': 'abc1',
|
||||
'user_nameid': 'abc1',
|
||||
|
@ -365,7 +367,7 @@ def test_datasource_users(pub):
|
|||
|
||||
# by uuid
|
||||
assert datasource.get_structured_value('abc0') == {
|
||||
'id': 1,
|
||||
'id': users[0].id,
|
||||
'text': 'John Doe 0',
|
||||
'user_name_identifier_0': 'abc0',
|
||||
'user_nameid': 'abc0',
|
||||
|
@ -376,7 +378,7 @@ def test_datasource_users(pub):
|
|||
}
|
||||
assert datasource.get_display_value('abc0') == 'John Doe 0'
|
||||
assert datasource.get_structured_value('abc1') == {
|
||||
'id': 2,
|
||||
'id': users[1].id,
|
||||
'text': 'John Doe 1',
|
||||
'user_name_identifier_0': 'abc1',
|
||||
'user_nameid': 'abc1',
|
||||
|
@ -388,8 +390,8 @@ def test_datasource_users(pub):
|
|||
assert datasource.get_display_value('abc1') == 'John Doe 1'
|
||||
|
||||
# by id
|
||||
assert datasource.get_structured_value('1') == {
|
||||
'id': 1,
|
||||
assert datasource.get_structured_value(str(users[0].id)) == {
|
||||
'id': users[0].id,
|
||||
'text': 'John Doe 0',
|
||||
'user_name_identifier_0': 'abc0',
|
||||
'user_nameid': 'abc0',
|
||||
|
@ -398,9 +400,9 @@ def test_datasource_users(pub):
|
|||
'user_display_name': 'John Doe 0',
|
||||
'user_email': None,
|
||||
}
|
||||
assert datasource.get_display_value('1') == 'John Doe 0'
|
||||
assert datasource.get_structured_value('2') == {
|
||||
'id': 2,
|
||||
assert datasource.get_display_value(str(users[0].id)) == 'John Doe 0'
|
||||
assert datasource.get_structured_value(str(users[1].id)) == {
|
||||
'id': users[1].id,
|
||||
'text': 'John Doe 1',
|
||||
'user_name_identifier_0': 'abc1',
|
||||
'user_nameid': 'abc1',
|
||||
|
@ -409,11 +411,11 @@ def test_datasource_users(pub):
|
|||
'user_display_name': 'John Doe 1',
|
||||
'user_email': None,
|
||||
}
|
||||
assert datasource.get_display_value('2') == 'John Doe 1'
|
||||
assert datasource.get_display_value(str(users[1].id)) == 'John Doe 1'
|
||||
|
||||
# by numeric id
|
||||
assert datasource.get_structured_value(1) == {
|
||||
'id': 1,
|
||||
assert datasource.get_structured_value(users[0].id) == {
|
||||
'id': users[0].id,
|
||||
'text': 'John Doe 0',
|
||||
'user_name_identifier_0': 'abc0',
|
||||
'user_nameid': 'abc0',
|
||||
|
@ -422,9 +424,9 @@ def test_datasource_users(pub):
|
|||
'user_display_name': 'John Doe 0',
|
||||
'user_email': None,
|
||||
}
|
||||
assert datasource.get_display_value(1) == 'John Doe 0'
|
||||
assert datasource.get_structured_value(2) == {
|
||||
'id': 2,
|
||||
assert datasource.get_display_value(users[0].id) == 'John Doe 0'
|
||||
assert datasource.get_structured_value(users[1].id) == {
|
||||
'id': users[1].id,
|
||||
'text': 'John Doe 1',
|
||||
'user_name_identifier_0': 'abc1',
|
||||
'user_nameid': 'abc1',
|
||||
|
@ -433,7 +435,7 @@ def test_datasource_users(pub):
|
|||
'user_display_name': 'John Doe 1',
|
||||
'user_email': None,
|
||||
}
|
||||
assert datasource.get_display_value(2) == 'John Doe 1'
|
||||
assert datasource.get_display_value(users[1].id) == 'John Doe 1'
|
||||
|
||||
datasource.users_included_roles = [role1.id]
|
||||
datasource.users_excluded_roles = [role2.id]
|
||||
|
@ -445,7 +447,7 @@ def test_datasource_users(pub):
|
|||
assert datasource.get_structured_value('abc0') is None
|
||||
assert datasource.get_display_value('abc0') is None
|
||||
assert datasource.get_structured_value('abc1') == {
|
||||
'id': 2,
|
||||
'id': users[1].id,
|
||||
'text': 'John Doe 1',
|
||||
'user_name_identifier_0': 'abc1',
|
||||
'user_nameid': 'abc1',
|
||||
|
@ -457,10 +459,10 @@ def test_datasource_users(pub):
|
|||
assert datasource.get_display_value('abc1') == 'John Doe 1'
|
||||
|
||||
# by id
|
||||
assert datasource.get_structured_value('1') is None
|
||||
assert datasource.get_display_value('1') is None
|
||||
assert datasource.get_structured_value('2') == {
|
||||
'id': 2,
|
||||
assert datasource.get_structured_value(str(users[0].id)) is None
|
||||
assert datasource.get_display_value(str(users[0].id)) is None
|
||||
assert datasource.get_structured_value(str(users[1].id)) == {
|
||||
'id': users[1].id,
|
||||
'text': 'John Doe 1',
|
||||
'user_name_identifier_0': 'abc1',
|
||||
'user_nameid': 'abc1',
|
||||
|
@ -469,7 +471,7 @@ def test_datasource_users(pub):
|
|||
'user_display_name': 'John Doe 1',
|
||||
'user_email': None,
|
||||
}
|
||||
assert datasource.get_display_value('2') == 'John Doe 1'
|
||||
assert datasource.get_display_value(str(users[1].id)) == 'John Doe 1'
|
||||
|
||||
datasource.include_disabled_users = False
|
||||
datasource.store()
|
||||
|
@ -483,10 +485,10 @@ def test_datasource_users(pub):
|
|||
assert datasource.get_display_value('abc1') is None
|
||||
|
||||
# by id
|
||||
assert datasource.get_structured_value('1') is None
|
||||
assert datasource.get_display_value('1') is None
|
||||
assert datasource.get_structured_value('2') is None
|
||||
assert datasource.get_display_value('2') is None
|
||||
assert datasource.get_structured_value(str(users[0].id)) is None
|
||||
assert datasource.get_display_value(str(users[0].id)) is None
|
||||
assert datasource.get_structured_value(str(users[1].id)) is None
|
||||
assert datasource.get_display_value(str(users[1].id)) is None
|
||||
|
||||
|
||||
def test_datasource_users_user_formdef(pub):
|
||||
|
@ -509,9 +511,9 @@ def test_datasource_users_user_formdef(pub):
|
|||
|
||||
assert data_sources.get_items({'type': datasource.slug}) == [
|
||||
(
|
||||
'3',
|
||||
str(user.id),
|
||||
'John Doe',
|
||||
'3',
|
||||
str(user.id),
|
||||
{
|
||||
'user_display_name': 'John Doe',
|
||||
'user_email': None,
|
||||
|
@ -520,8 +522,67 @@ def test_datasource_users_user_formdef(pub):
|
|||
'user_var_plop': 'Bar',
|
||||
'user_admin_access': False,
|
||||
'user_backoffice_access': False,
|
||||
'id': 3,
|
||||
'id': user.id,
|
||||
'text': 'John Doe',
|
||||
},
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def test_legacy_format_import(pub):
|
||||
data_source_xml = """<datasource id="255">
|
||||
<name>Agents de la ville</name>
|
||||
<slug>agents_de_la_ville</slug>
|
||||
<data_source>
|
||||
<type>wcs:users</type>
|
||||
<value />
|
||||
</data_source><users_included_roles>
|
||||
<item>8201764fc2c24b92bd691fd231a4cf76</item>
|
||||
</users_included_roles>
|
||||
</datasource>"""
|
||||
ds = NamedDataSource.import_from_xml_tree(ET.fromstring(data_source_xml))
|
||||
assert ds.users_included_roles == ['8201764fc2c24b92bd691fd231a4cf76']
|
||||
|
||||
|
||||
def test_new_format_import(pub):
|
||||
data_source_xml = """<datasource id="255">
|
||||
<name>Agents de la ville</name>
|
||||
<slug>agents_de_la_ville</slug>
|
||||
<data_source>
|
||||
<type>wcs:users</type>
|
||||
<value />
|
||||
</data_source><users_included_roles>
|
||||
<role role-id="8201764fc2c24b92bd691fd231a4cf76" role-slug="agent">Agents</role>
|
||||
</users_included_roles>
|
||||
</datasource>"""
|
||||
ds = NamedDataSource.import_from_xml_tree(ET.fromstring(data_source_xml))
|
||||
assert ds.users_included_roles == [] # role doesn't exist
|
||||
|
||||
# import with id match
|
||||
pub.role_class.wipe()
|
||||
role1 = pub.role_class(name='role')
|
||||
role1.id = '8201764fc2c24b92bd691fd231a4cf76'
|
||||
role1.store()
|
||||
|
||||
ds = NamedDataSource.import_from_xml_tree(ET.fromstring(data_source_xml), include_id=True)
|
||||
assert ds.users_included_roles == [role1.id]
|
||||
|
||||
# import with slug match
|
||||
pub.role_class.wipe()
|
||||
role1 = pub.role_class(name='Agents')
|
||||
role1.slug = 'agent'
|
||||
role1.store()
|
||||
|
||||
ds = NamedDataSource.import_from_xml_tree(ET.fromstring(data_source_xml), include_id=False)
|
||||
assert ds.users_included_roles == [role1.id]
|
||||
|
||||
# import with name match
|
||||
pub.role_class.wipe()
|
||||
role1 = pub.role_class(name='Agents')
|
||||
role1.slug = 'agent'
|
||||
role1.store()
|
||||
|
||||
ds = NamedDataSource.import_from_xml_tree(
|
||||
ET.fromstring(data_source_xml.replace('role-slug="agent"', '')), include_id=False
|
||||
)
|
||||
assert ds.users_included_roles == [role1.id]
|
||||
|
|
|
@ -153,11 +153,11 @@ def test_text(pub):
|
|||
|
||||
form = Form(use_tokens=False)
|
||||
fields.TextField(display_mode='rich').add_to_form(form)
|
||||
assert 'data-godo-schema="full"' in str(form.render())
|
||||
assert PyQuery(str(form.render()))('godo-editor[schema=full]')
|
||||
|
||||
form = Form(use_tokens=False)
|
||||
fields.TextField(display_mode='basic-rich').add_to_form(form)
|
||||
assert 'data-godo-schema="basic"' in str(form.render())
|
||||
assert PyQuery(str(form.render()))('godo-editor[schema=basic]')
|
||||
|
||||
|
||||
def test_text_anonymise(pub):
|
||||
|
@ -915,6 +915,17 @@ def test_file_convert_from_anything():
|
|||
assert value.get_file_pointer().read() == b'hello'
|
||||
|
||||
|
||||
def test_file_from_json_value(pub):
|
||||
value = fields.FileField().from_json_value({'content': 'aGVsbG8=', 'filename': 'test.txt'})
|
||||
assert value.base_filename == 'test.txt'
|
||||
assert value.get_file_pointer().read() == b'hello'
|
||||
|
||||
value = fields.FileField().from_json_value(
|
||||
{'content': 'aGVsbG8', 'filename': 'test.txt'} # invalid padding
|
||||
)
|
||||
assert value is None
|
||||
|
||||
|
||||
def test_new_field_type_options(pub):
|
||||
pub.load_site_options()
|
||||
if not pub.site_options.has_section('options'):
|
||||
|
|
|
@ -9,6 +9,7 @@ from unittest import mock
|
|||
|
||||
import pytest
|
||||
from django.utils import formats
|
||||
from django.utils.timezone import localtime, make_aware
|
||||
from quixote import get_publisher, get_request
|
||||
from quixote.http_request import Upload
|
||||
|
||||
|
@ -392,7 +393,7 @@ def test_get_last_update_time(pub, formdef):
|
|||
|
||||
time.sleep(1)
|
||||
evo = Evolution(formdata=formdata)
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
evo.status = formdata.status
|
||||
evo.comment = 'hello world'
|
||||
formdata.evolution.append(evo)
|
||||
|
@ -442,13 +443,13 @@ def test_clean_drafts(pub):
|
|||
|
||||
d = formdef.data_class()()
|
||||
d.status = 'draft'
|
||||
d.receipt_time = time.localtime()
|
||||
d.receipt_time = localtime()
|
||||
d.store()
|
||||
d_id1 = d.id
|
||||
|
||||
d = formdef.data_class()()
|
||||
d.status = 'draft'
|
||||
d.receipt_time = time.localtime(0) # epoch, 1970-01-01
|
||||
d.receipt_time = make_aware(datetime.datetime(1970, 1, 1))
|
||||
d.store()
|
||||
|
||||
assert formdef.data_class().count() == 2
|
||||
|
@ -460,7 +461,7 @@ def test_clean_drafts(pub):
|
|||
|
||||
d = formdef.data_class()()
|
||||
d.status = 'draft'
|
||||
d.receipt_time = time.localtime(time.time() - 86400 * 5)
|
||||
d.receipt_time = localtime() - datetime.timedelta(days=5)
|
||||
d.store()
|
||||
clean_drafts(pub)
|
||||
assert formdef.data_class().count() == 2
|
||||
|
@ -575,9 +576,9 @@ def test_get_json_export_dict_evolution(pub, local_user):
|
|||
d = formdef.data_class()()
|
||||
d.status = 'wf-%s' % st_new.id
|
||||
d.user_id = local_user.id
|
||||
d.receipt_time = time.localtime()
|
||||
d.receipt_time = localtime()
|
||||
evo = Evolution(formdata=d)
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
evo.status = 'wf-%s' % st_new.id
|
||||
evo.who = '_submitter'
|
||||
d.evolution = [evo]
|
||||
|
@ -587,7 +588,7 @@ def test_get_json_export_dict_evolution(pub, local_user):
|
|||
evo.add_part(JournalAssignationErrorPart('summary', 'label'))
|
||||
d.store()
|
||||
evo = Evolution(formdata=d)
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
evo.status = 'wf-%s' % st_finished.id
|
||||
evo.who = '_submitter'
|
||||
d.evolution.append(evo)
|
||||
|
@ -731,25 +732,25 @@ def test_evolution_get_status(pub):
|
|||
d.evolution = []
|
||||
|
||||
evo = Evolution(formdata=d)
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
evo.status = 'wf-%s' % st_new.id
|
||||
d.evolution.append(evo)
|
||||
|
||||
evo = Evolution(formdata=d)
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
d.evolution.append(evo)
|
||||
|
||||
evo = Evolution(formdata=d)
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
d.evolution.append(evo)
|
||||
|
||||
evo = Evolution(formdata=d)
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
evo.status = 'wf-%s' % st_finished.id
|
||||
d.evolution.append(evo)
|
||||
|
||||
evo = Evolution(formdata=d)
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
d.evolution.append(evo)
|
||||
|
||||
d.store()
|
||||
|
@ -833,9 +834,9 @@ def test_lazy_formdata(pub, variable_test_data):
|
|||
formdef = FormDef.select()[0]
|
||||
formdata = formdef.data_class().select()[0]
|
||||
lazy_formdata = LazyFormData(formdata)
|
||||
assert lazy_formdata.receipt_date == time.strftime('%Y-%m-%d', formdata.receipt_time)
|
||||
assert lazy_formdata.receipt_time == formats.time_format(datetime.datetime(*formdata.receipt_time[:6]))
|
||||
assert lazy_formdata.last_update_datetime.timetuple()[:6] == formdata.last_update_time[:6]
|
||||
assert lazy_formdata.receipt_date == formdata.receipt_time.strftime('%Y-%m-%d')
|
||||
assert lazy_formdata.receipt_time == formats.time_format(formdata.receipt_time)
|
||||
assert lazy_formdata.last_update_datetime.timetuple()[:6] == formdata.last_update_time.timetuple()[:6]
|
||||
assert lazy_formdata.internal_id == formdata.id
|
||||
assert lazy_formdata.name == 'foobarlazy'
|
||||
assert lazy_formdata.display_name == 'foobarlazy #%s' % formdata.get_display_id()
|
||||
|
@ -846,6 +847,7 @@ def test_lazy_formdata(pub, variable_test_data):
|
|||
assert lazy_formdata.backoffice_submission_url == formdef.get_backoffice_submission_url()
|
||||
assert lazy_formdata.frontoffice_submission_url == formdef.get_url()
|
||||
assert lazy_formdata.api_url == formdata.get_api_url()
|
||||
assert lazy_formdata.short_url == formdata.get_short_url()
|
||||
assert lazy_formdata.attachments
|
||||
assert lazy_formdata.geoloc['base'] == {'lat': 1, 'lon': 2}
|
||||
assert lazy_formdata.geoloc['base_lon'] == 2
|
||||
|
@ -1995,6 +1997,31 @@ def test_lazy_formdata_queryset_filter_non_unique_varname(pub, variable_test_dat
|
|||
assert tmpl.render(context) == '1'
|
||||
|
||||
|
||||
def test_filter_on_page_field(pub):
|
||||
pub.loggederror_class.wipe()
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test'
|
||||
formdef.fields = [
|
||||
fields.PageField(id='1', label='Page', varname='page'),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
data_class = formdef.data_class()
|
||||
formdata = data_class()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
context = pub.substitutions.get_context_variables(mode='lazy')
|
||||
|
||||
tmpl = Template('{{forms|objects:"test"|filter_by:"page"|filter_value:"100"}}')
|
||||
tmpl.render(context)
|
||||
assert pub.loggederror_class.count() == 1
|
||||
logged_error = pub.loggederror_class.select()[0]
|
||||
assert logged_error.summary == 'Invalid filter "page"'
|
||||
|
||||
|
||||
def test_numeric_filter_on_string(pub):
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
|
@ -3740,6 +3767,16 @@ def test_formdata_user_field(pub, variable_test_data):
|
|||
assert condition.evaluate() is False
|
||||
|
||||
|
||||
def test_formdata_user_has_deleted_account(pub, variable_test_data):
|
||||
condition = Condition({'type': 'django', 'value': 'form_user_has_deleted_account'})
|
||||
assert condition.evaluate() is False
|
||||
|
||||
local_user = variable_test_data._formdata.user
|
||||
local_user.set_deleted()
|
||||
condition = Condition({'type': 'django', 'value': 'form_user_has_deleted_account'})
|
||||
assert condition.evaluate() is True
|
||||
|
||||
|
||||
def test_string_filters(pub, variable_test_data):
|
||||
tmpl = Template('{% with form_var_foo_foo|split:"a" as x %}{{x.0}}{% endwith %}', raises=True)
|
||||
for mode in (None, 'lazy'):
|
||||
|
@ -4234,6 +4271,8 @@ def test_formdata_filtering_on_fields(pub):
|
|||
('between', 'plop5|plop1', '4'),
|
||||
('between', ['plop1', 'plop5'], '4'),
|
||||
('between', ['plop5', 'plop1'], '4'),
|
||||
('icontains', 'plop', '10'),
|
||||
('icontains', 'PLOP', '10'),
|
||||
]
|
||||
for operator, value, result in params:
|
||||
context['value'] = None
|
||||
|
@ -4492,6 +4531,8 @@ def test_formdata_filtering_on_fields(pub):
|
|||
('not_in', 'a@localhost|b@localhost', '1'),
|
||||
('absent', '', '2'),
|
||||
('existing', '', '10'),
|
||||
('icontains', 'A@local', '5'),
|
||||
('icontains', '@LOCAL', '10'),
|
||||
]
|
||||
for operator, value, result in params:
|
||||
if value:
|
||||
|
@ -4541,6 +4582,8 @@ def test_formdata_filtering_on_fields(pub):
|
|||
('between', 'plop5|plop1', '4'),
|
||||
('between', ['plop1', 'plop5'], '4'),
|
||||
('between', ['plop5', 'plop1'], '4'),
|
||||
('icontains', 'plop', '10'),
|
||||
('icontains', 'PLOP', '10'),
|
||||
]
|
||||
for operator, value, result in params:
|
||||
context['value'] = None
|
||||
|
@ -5310,10 +5353,15 @@ def test_fts_phone(pub):
|
|||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
assert formdef.data_class().count([FtsMatch('01 23 45 67 89')]) == 1
|
||||
assert formdef.data_class().count([FtsMatch('0123456789')]) == 1
|
||||
assert formdef.data_class().count([FtsMatch('+33123456789')]) == 1
|
||||
assert formdef.data_class().count([FtsMatch('+33(0)123456789')]) == 1
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'1': None, '2': '0123456789'}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
assert formdef.data_class().count([FtsMatch('01 23 45 67 89')]) == 2
|
||||
assert formdef.data_class().count([FtsMatch('0123456789')]) == 2
|
||||
assert formdef.data_class().count([FtsMatch('+33123456789')]) == 2
|
||||
assert formdef.data_class().count([FtsMatch('+33(0)123456789')]) == 2
|
||||
assert formdef.data_class().count([FtsMatch('+33(0)123456789 foo')]) == 1
|
||||
assert formdef.data_class().count([FtsMatch('+33(0)123456789 bar')]) == 0
|
||||
assert formdef.data_class().count([FtsMatch('foo +33(0)123456789')]) == 1
|
||||
|
@ -5368,7 +5416,7 @@ def test_get_visible_status(pub, local_user):
|
|||
# create evolution [new, empty, finished, empty]
|
||||
for status in (st_new, None, st_finished, None):
|
||||
evo = Evolution(formdata=formdata)
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
if status:
|
||||
evo.status = 'wf-%s' % status.id
|
||||
formdata.evolution.append(evo)
|
||||
|
@ -5629,7 +5677,7 @@ def test_get_status_datetime(pub, freezer):
|
|||
|
||||
freezer.move_to(datetime.datetime(2023, 10, 31, 12, 0))
|
||||
evo = Evolution(formdata=formdata)
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
formdata.evolution.append(evo)
|
||||
assert formdata.get_status_datetime(status=st_next) == formdata.evolution[1].time
|
||||
assert formdata.get_status_datetime(status=st_next, latest=True) == formdata.evolution[1].time
|
||||
|
@ -5748,3 +5796,29 @@ def test_reverse_links(pub):
|
|||
assert context['form_reverse_links_formdef_foobar_bar_1_form_internal_id'] == formdata2.id
|
||||
assert len(context['form_reverse_links_carddef_card_2_foo']) == 1
|
||||
assert context['form_reverse_links_carddef_card_2_foo_0_form_internal_id'] == carddata2.id
|
||||
|
||||
|
||||
def test_no_short_url(pub):
|
||||
CardDef.wipe()
|
||||
carddef = CardDef()
|
||||
carddef.name = 'card'
|
||||
carddef.store()
|
||||
carddata = carddef.data_class()()
|
||||
carddata.just_created()
|
||||
carddata.store()
|
||||
|
||||
lazy_carddata = LazyFormData(carddata)
|
||||
assert 'short_url' not in lazy_carddata.inspect_keys()
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form'
|
||||
formdef.store()
|
||||
formdata = formdef.data_class()()
|
||||
|
||||
lazy_formdata = LazyFormData(formdata)
|
||||
assert 'short_url' not in lazy_formdata.inspect_keys()
|
||||
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
assert 'short_url' in lazy_formdata.inspect_keys()
|
||||
|
|
|
@ -19,6 +19,7 @@ from wcs.fields import DateField, ItemField, StringField
|
|||
from wcs.formdef import FormDef, get_formdefs_of_all_kinds, update_storage_all_formdefs
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.qommon.upload_storage import PicklableUpload
|
||||
from wcs.variables import LazyFormDef
|
||||
from wcs.wf.form import FormWorkflowStatusItem, WorkflowFormEvolutionPart, WorkflowFormFieldsFormDef
|
||||
from wcs.workflows import (
|
||||
AttachmentEvolutionPart,
|
||||
|
@ -662,3 +663,39 @@ def test_update_storage_all_formdefs(pub):
|
|||
with mock.patch('wcs.formdef.FormDef.update_storage') as update_storage:
|
||||
update_storage_all_formdefs(pub)
|
||||
assert update_storage.call_count == 10
|
||||
|
||||
|
||||
def test_lazy_formdef(pub):
|
||||
FormDef.wipe()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test form'
|
||||
|
||||
lazy_formdef = LazyFormDef(formdef)
|
||||
assert lazy_formdef.publication_disabled is False
|
||||
assert lazy_formdef.publication_datetime is None
|
||||
assert lazy_formdef.publication_expiration_datetime is None
|
||||
|
||||
formdef.disabled = True
|
||||
assert lazy_formdef.publication_disabled is True
|
||||
assert lazy_formdef.publication_datetime is None
|
||||
assert lazy_formdef.publication_expiration_datetime is None
|
||||
|
||||
formdef.disabled = False
|
||||
formdef.publication_date = '2000-01-01'
|
||||
assert lazy_formdef.publication_disabled is False
|
||||
assert lazy_formdef.publication_datetime == datetime.datetime(2000, 1, 1)
|
||||
assert lazy_formdef.publication_expiration_datetime is None
|
||||
|
||||
formdef.disabled = False
|
||||
formdef.publication_date = '2200-01-01'
|
||||
assert lazy_formdef.publication_disabled is True
|
||||
assert lazy_formdef.publication_datetime == datetime.datetime(2200, 1, 1)
|
||||
assert lazy_formdef.publication_expiration_datetime is None
|
||||
|
||||
formdef.disabled = False
|
||||
formdef.publication_date = '2000-01-01'
|
||||
formdef.expiration_date = '2000-01-01 10:00'
|
||||
assert lazy_formdef.publication_disabled is True
|
||||
assert lazy_formdef.publication_datetime == datetime.datetime(2000, 1, 1)
|
||||
assert lazy_formdef.publication_expiration_datetime == datetime.datetime(2000, 1, 1, 10, 0)
|
||||
|
|
|
@ -2,6 +2,7 @@ import decimal
|
|||
import io
|
||||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
|
@ -154,18 +155,28 @@ def test_more_fields(pub):
|
|||
fields.BoolField(label='Bar'),
|
||||
fields.DateField(label='Bar', minimum_date='2014-01-01'),
|
||||
fields.ItemField(label='Bar', items=['foo', 'bar', 'baz']),
|
||||
fields.NumericField(label='Bar', min_value=Decimal(-12), max_value=Decimal(12)),
|
||||
fields.NumericField(label='Bar', min_value=None, max_value=None),
|
||||
]
|
||||
f2 = assert_xml_import_export_works(formdef)
|
||||
assert len(f2.fields) == len(formdef.fields)
|
||||
assert f2.fields[2].key == formdef.fields[2].key
|
||||
assert f2.fields[3].minimum_date == formdef.fields[3].minimum_date
|
||||
assert f2.fields[4].items == formdef.fields[4].items
|
||||
assert f2.fields[5].min_value == formdef.fields[5].min_value
|
||||
assert f2.fields[5].max_value == formdef.fields[5].max_value
|
||||
assert f2.fields[6].min_value == formdef.fields[6].min_value
|
||||
assert f2.fields[6].max_value == formdef.fields[6].max_value
|
||||
|
||||
f2 = assert_json_import_export_works(formdef)
|
||||
assert len(f2.fields) == len(formdef.fields)
|
||||
assert f2.fields[2].key == formdef.fields[2].key
|
||||
assert f2.fields[3].minimum_date == formdef.fields[3].minimum_date
|
||||
assert f2.fields[4].items == formdef.fields[4].items
|
||||
assert f2.fields[5].min_value == formdef.fields[5].min_value
|
||||
assert f2.fields[5].max_value == formdef.fields[5].max_value
|
||||
assert f2.fields[6].min_value == formdef.fields[6].min_value
|
||||
assert f2.fields[6].max_value == formdef.fields[6].max_value
|
||||
|
||||
|
||||
def test_item_radio(pub):
|
||||
|
@ -1066,3 +1077,12 @@ def test_tracking_code_attributes(pub):
|
|||
assert f2.enable_tracking_codes == formdef.enable_tracking_codes
|
||||
assert f2.tracking_code_verify_fields == formdef.tracking_code_verify_fields
|
||||
assert f2.confirmation == formdef.confirmation
|
||||
|
||||
|
||||
def test_management_sidebar_items(pub):
|
||||
formdef = FormDef()
|
||||
formdef.name = 'Foo'
|
||||
formdef.url_name = 'foo'
|
||||
formdef.management_sidebar_items = {'general', 'pending-forms'}
|
||||
f2 = assert_xml_import_export_works(formdef)
|
||||
assert f2.management_sidebar_items == {'general', 'pending-forms'}
|
||||
|
|
|
@ -2,12 +2,14 @@ import copy
|
|||
import json
|
||||
import os
|
||||
import pickle
|
||||
import random
|
||||
import shutil
|
||||
import tempfile
|
||||
import urllib.parse
|
||||
import zipfile
|
||||
from unittest import mock
|
||||
|
||||
import psycopg2
|
||||
import pytest
|
||||
from django.core.management import call_command
|
||||
from quixote import cleanup
|
||||
|
@ -74,6 +76,12 @@ HOBO_JSON = {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
'service-id': 'lingo',
|
||||
'title': 'Lingo',
|
||||
'base_url': 'http://payment.example.net/',
|
||||
'secret_key': 'aaa',
|
||||
},
|
||||
],
|
||||
'profile': {
|
||||
'fields': [
|
||||
|
@ -257,18 +265,28 @@ def deploy_setup(alt_tempdir):
|
|||
del hobo_json['services'][1] # authentic
|
||||
fd.write(json.dumps(hobo_json))
|
||||
skeleton_dir = os.path.join(CompatWcsPublisher.APP_DIR, 'skeletons')
|
||||
if not os.path.exists(skeleton_dir):
|
||||
os.mkdir(skeleton_dir)
|
||||
with open(os.path.join(skeleton_dir, 'export-test.wcs'), 'wb') as f:
|
||||
with zipfile.ZipFile(f, 'w') as z:
|
||||
CONFIG = {
|
||||
'postgresql': {
|
||||
'createdb-connection-params': {'database': 'postgres', 'user': os.environ['USER']},
|
||||
'database-template-name': 'wcstests_hobo_%s',
|
||||
'user': os.environ['USER'],
|
||||
}
|
||||
os.mkdir(skeleton_dir)
|
||||
db_template_name = 'wcstests_hobo_%d_%%s' % random.randint(0, 100000)
|
||||
with open(os.path.join(skeleton_dir, 'export-test.wcs'), 'wb') as f:
|
||||
with zipfile.ZipFile(f, 'w') as z:
|
||||
CONFIG = {
|
||||
'postgresql': {
|
||||
'createdb-connection-params': {'database': 'postgres', 'user': os.environ['USER']},
|
||||
'database-template-name': db_template_name,
|
||||
'user': os.environ['USER'],
|
||||
}
|
||||
z.writestr('config.json', json.dumps(CONFIG))
|
||||
}
|
||||
z.writestr('config.json', json.dumps(CONFIG))
|
||||
yield True
|
||||
shutil.rmtree(skeleton_dir)
|
||||
|
||||
conn = psycopg2.connect(user=os.environ['USER'], dbname='postgres')
|
||||
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
|
||||
cur = conn.cursor()
|
||||
cur.execute('DROP DATABASE IF EXISTS %s' % db_template_name % 'wcs_example_net')
|
||||
cur.execute('DROP DATABASE IF EXISTS %s' % db_template_name % 'wcs2_example_net')
|
||||
cur.close()
|
||||
conn.commit()
|
||||
|
||||
|
||||
def test_configure_site_options(setuptest, alt_tempdir):
|
||||
|
@ -281,6 +299,7 @@ def test_configure_site_options(setuptest, alt_tempdir):
|
|||
assert pub.get_site_option('xxx', 'variables') == 'HELLO WORLD'
|
||||
assert pub.get_site_option('portal_agent_url', 'variables') == 'http://agents.example.net/'
|
||||
assert pub.get_site_option('portal_url', 'variables') == 'http://portal.example.net/'
|
||||
assert pub.get_site_option('lingo_url', 'variables') == 'http://payment.example.net/'
|
||||
assert pub.get_site_option('test_wcs_url', 'variables') == 'http://wcs.example.net/'
|
||||
assert pub.get_site_option('disable_cron_jobs', 'variables') == 'True'
|
||||
assert pub.get_site_option('maintenance_page', 'variables') == 'True'
|
||||
|
@ -519,6 +538,8 @@ def test_deploy(setuptest, alt_tempdir, deploy_setup, settings):
|
|||
with open(os.path.join(alt_tempdir, 'tenants', 'wcs.example.net', 'config.pck'), 'rb') as fd:
|
||||
pub_cfg = pickle.load(fd)
|
||||
assert pub_cfg['language'] == {'language': 'fr'}
|
||||
cleanup_connection()
|
||||
cleanup()
|
||||
|
||||
|
||||
def test_configure_postgresql(setuptest, alt_tempdir, deploy_setup, settings):
|
||||
|
@ -634,6 +655,8 @@ def test_redeploy(setuptest, alt_tempdir, deploy_setup, settings):
|
|||
'http://wcs.example.net/',
|
||||
'http://wcs2.example.net/',
|
||||
}
|
||||
cleanup_connection()
|
||||
cleanup()
|
||||
|
||||
|
||||
def test_configure_site_options_legacy_urls(setuptest, alt_tempdir):
|
||||
|
|
|
@ -474,6 +474,7 @@ PROFILE = {
|
|||
|
||||
def test_process_notification_user_provision(pub):
|
||||
User = pub.user_class
|
||||
User.wipe()
|
||||
|
||||
# create some roles
|
||||
from wcs.ctl.management.commands.hobo_deploy import Command
|
||||
|
|
|
@ -39,7 +39,7 @@ def test_translation_columns(pub):
|
|||
assert not column_exists_in_table(cur, 'translatable_messages', 'string_fr')
|
||||
pub.cfg['language'] = {'language': 'en', 'multilinguism': True, 'languages': ['fr', 'de']}
|
||||
pub.write_cfg()
|
||||
TranslatableMessage.do_table()
|
||||
TranslatableMessage.do_table() # update table with selected languages
|
||||
assert column_exists_in_table(cur, 'translatable_messages', 'string_de')
|
||||
assert column_exists_in_table(cur, 'translatable_messages', 'string_fr')
|
||||
# check it's not removed
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import datetime
|
||||
import decimal
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
|
@ -27,6 +28,7 @@ from wcs.qommon.misc import (
|
|||
format_time,
|
||||
get_as_datetime,
|
||||
normalize_geolocation,
|
||||
parse_decimal,
|
||||
parse_isotime,
|
||||
simplify,
|
||||
validate_phone_fr,
|
||||
|
@ -691,11 +693,12 @@ def test_http_request_url_switch(mock_request, pub):
|
|||
mock_request.reset_mock()
|
||||
|
||||
|
||||
def test_validate_phone_fr():
|
||||
def test_validate_phone_fr(pub):
|
||||
valid = [
|
||||
'0123456789',
|
||||
'+33123456789',
|
||||
'+590690000102',
|
||||
'06 92 32 00 00', # valid number in (+262) but not in (+33)
|
||||
]
|
||||
invalid = [
|
||||
'1234559',
|
||||
|
@ -706,3 +709,49 @@ def test_validate_phone_fr():
|
|||
|
||||
assert all(validate_phone_fr(pn) for pn in valid)
|
||||
assert all(not validate_phone_fr(pn) for pn in invalid)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value, expected',
|
||||
[
|
||||
('1.3', decimal.Decimal('1.3')),
|
||||
('1,5', decimal.Decimal(1.5)),
|
||||
(True, decimal.Decimal(0)),
|
||||
(False, decimal.Decimal(0)),
|
||||
(None, 0),
|
||||
('', 0),
|
||||
],
|
||||
ids=['1.3', '1,5', 'True', 'False', 'None', 'empty-string'],
|
||||
)
|
||||
def test_parse_decimal_base(value, expected):
|
||||
assert parse_decimal(value) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value, expected',
|
||||
[
|
||||
('1.3', decimal.Decimal('1.3')),
|
||||
('1,5', decimal.Decimal(1.5)),
|
||||
(True, decimal.Decimal(0)),
|
||||
(False, decimal.Decimal(0)),
|
||||
(None, None),
|
||||
('', None),
|
||||
],
|
||||
ids=['1.3', '1,5', 'True', 'False', 'None', 'empty-string'],
|
||||
)
|
||||
def test_parse_decimal_keep_none(value, expected):
|
||||
assert parse_decimal(value, keep_none=True) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'value, exception',
|
||||
[
|
||||
(None, TypeError),
|
||||
('', decimal.InvalidOperation),
|
||||
('xyz', decimal.InvalidOperation),
|
||||
],
|
||||
ids=['None', 'empty-string', 'alpha'],
|
||||
)
|
||||
def test_parse_decimal_do_raise(value, exception):
|
||||
with pytest.raises(exception):
|
||||
parse_decimal(value, do_raise=True)
|
||||
|
|
|
@ -246,3 +246,21 @@ def test_postgresql_down(pub):
|
|||
with mock.patch('psycopg2.connect', side_effect=psycopg2.OperationalError()):
|
||||
resp = get_app(pub).get('/', status=503)
|
||||
assert 'Error connecting to database' in resp.text
|
||||
|
||||
|
||||
def test_short_url_redirect(pub, formdef1):
|
||||
formdata = formdef1.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
app = get_app(pub)
|
||||
app.get('/r/xxx', status=404)
|
||||
app.get('/r/300', status=404)
|
||||
app.get('/r/300-100', status=404)
|
||||
resp = app.get(f'/r/{formdef1.id}', status=302)
|
||||
assert resp.location == formdef1.get_url()
|
||||
resp = app.get(f'/r/{formdef1.id}-{formdata.id}', status=302)
|
||||
assert resp.location == formdata.get_url()
|
||||
assert formdata.get_short_url() == f'http://example.net/r/{formdef1.id}-{formdata.id}'
|
||||
resp = app.get(formdata.get_short_url(), status=302)
|
||||
assert resp.location == formdata.get_url()
|
||||
|
|
|
@ -68,6 +68,7 @@ session_max_age: 1
|
|||
|
||||
|
||||
def test_session_expire(pub, user, app):
|
||||
pub.session_manager.session_class.wipe()
|
||||
login(app, username='foo', password='foo')
|
||||
assert 'Logout' in app.get('/')
|
||||
session = pub.session_manager.session_class.select()[0]
|
||||
|
|
|
@ -826,6 +826,11 @@ def test_workflow_snapshot_browse(pub):
|
|||
workflow.store()
|
||||
assert pub.snapshot_class.count() == 1
|
||||
|
||||
# create a new snapshot
|
||||
workflow.name = 'new name'
|
||||
workflow.store()
|
||||
assert pub.snapshot_class.count() == 2
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/workflows/%s/history/' % workflow.id)
|
||||
|
@ -835,6 +840,18 @@ def test_workflow_snapshot_browse(pub):
|
|||
assert '<p>%s</p>' % localstrftime(snapshot.timestamp) in resp.text
|
||||
|
||||
# check restore/export links of sidebar
|
||||
# latest version has its restore link disabled
|
||||
assert [(x.text, 'disabled' in x.attrib['class']) for x in resp.pyquery('#sidebar [role="button"]')] == [
|
||||
('Restore version', True),
|
||||
('Export version', False),
|
||||
('Inspect version', False),
|
||||
]
|
||||
resp = resp.click('>')
|
||||
assert [(x.text, 'disabled' in x.attrib['class']) for x in resp.pyquery('#sidebar [role="button"]')] == [
|
||||
('Restore version', False),
|
||||
('Export version', False),
|
||||
('Inspect version', False),
|
||||
]
|
||||
resp.click('Restore version')
|
||||
resp_export = resp.click('Export version')
|
||||
assert 'snapshot-workflow' in resp_export.headers['Content-Disposition']
|
||||
|
@ -933,6 +950,10 @@ def test_workflow_snapshot_restore(pub):
|
|||
workflow = Workflow(name='test')
|
||||
workflow.store()
|
||||
|
||||
# create a new snapshot
|
||||
workflow.name = 'new name'
|
||||
workflow.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/workflows/%s/history/' % workflow.id)
|
||||
|
@ -1069,6 +1090,10 @@ def test_workflow_with_model_snapshot_browse(pub):
|
|||
== 1 + i
|
||||
)
|
||||
|
||||
# create a new snapshot
|
||||
workflow.name = 'new name'
|
||||
workflow.store()
|
||||
|
||||
resp = app.get('/backoffice/workflows/%s/history/' % workflow.id)
|
||||
resp = resp.click(href='%s/restore' % snapshot.id)
|
||||
assert resp.form['action'].value == 'as-new'
|
||||
|
@ -1410,25 +1435,49 @@ def test_category_snapshot_browse(pub):
|
|||
|
||||
Category.wipe()
|
||||
category = Category(name='test')
|
||||
category.position = 42
|
||||
category.store()
|
||||
assert pub.snapshot_class.count() == 1
|
||||
# check calling .store() without changes doesn't create snapshots
|
||||
category.store()
|
||||
assert pub.snapshot_class.count() == 1
|
||||
category.name = 'foobar'
|
||||
category.store()
|
||||
assert pub.snapshot_class.count() == 2
|
||||
|
||||
app = login(get_app(pub))
|
||||
|
||||
resp = app.get('/backoffice/forms/categories/%s/' % category.id)
|
||||
resp = resp.click('History')
|
||||
snapshot = pub.snapshot_class.select_object_history(category)[0]
|
||||
snapshot = pub.snapshot_class.select_object_history(category)[1]
|
||||
snapshot = snapshot.get_latest(
|
||||
snapshot.object_type, snapshot.object_id, complete=True, max_timestamp=snapshot.timestamp
|
||||
)
|
||||
assert snapshot.patch is None
|
||||
assert 'position' not in snapshot.serialization
|
||||
resp = resp.click(href='%s/view/' % snapshot.id)
|
||||
assert 'This category is readonly' in resp.text
|
||||
assert 'inspect' not in resp
|
||||
assert '<p>%s</p>' % localstrftime(snapshot.timestamp) in resp.text
|
||||
with pytest.raises(IndexError):
|
||||
resp = resp.click('Edit')
|
||||
resp.click('Edit')
|
||||
resp = app.get('/backoffice/forms/categories/%s/' % category.id)
|
||||
resp = resp.click('History')
|
||||
resp = resp.click(href='%s/restore' % snapshot.id)
|
||||
assert resp.form['action'].value == 'as-new'
|
||||
resp = resp.form.submit('submit')
|
||||
assert Category.count() == 2
|
||||
new_category = Category.get(resp.location.split('/')[-2])
|
||||
assert new_category.position == 43
|
||||
|
||||
resp = app.get('/backoffice/forms/categories/%s/history/%s/view/' % (category.id, snapshot.id))
|
||||
assert 'inspect' not in resp
|
||||
resp = app.get('/backoffice/forms/categories/%s/' % category.id)
|
||||
resp = resp.click('History')
|
||||
resp = resp.click(href='%s/restore' % snapshot.id)
|
||||
resp.form['action'].value = 'overwrite'
|
||||
resp = resp.form.submit('submit')
|
||||
assert Category.count() == 2
|
||||
category.refresh_from_storage()
|
||||
assert category.position == 42
|
||||
|
||||
|
||||
def test_snapshots_test_results(pub):
|
||||
|
|
|
@ -10,6 +10,7 @@ import zipfile
|
|||
|
||||
import psycopg2
|
||||
import pytest
|
||||
from django.utils.timezone import localtime, make_aware
|
||||
from django.utils.timezone import now as tz_now
|
||||
|
||||
import wcs.sql_criterias as st
|
||||
|
@ -20,7 +21,6 @@ from wcs.data_sources import NamedDataSource
|
|||
from wcs.formdata import Evolution
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.qommon import force_str
|
||||
from wcs.testdef import TestDef
|
||||
from wcs.wf.register_comment import RegisterCommenterWorkflowStatusItem
|
||||
from wcs.workflows import (
|
||||
ActionsTracingEvolutionPart,
|
||||
|
@ -390,7 +390,7 @@ def test_sql_evolution(formdef):
|
|||
assert len(formdata.evolution) == 1
|
||||
|
||||
evo = Evolution(formdata=formdata)
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
evo.status = formdata.status
|
||||
evo.comment = 'hello world'
|
||||
formdata.evolution.append(evo)
|
||||
|
@ -412,7 +412,7 @@ def test_sql_evolution_change(formdef):
|
|||
assert len(formdata.evolution) == 1
|
||||
|
||||
evo = Evolution(formdata=formdata)
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
evo.status = formdata.status
|
||||
evo.comment = 'hello world'
|
||||
formdata.evolution.append(evo)
|
||||
|
@ -441,7 +441,7 @@ def test_sql_multiple_evolutions(formdef):
|
|||
formdata = data_class.get(id)
|
||||
|
||||
evo = Evolution(formdata=formdata)
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
evo.status = formdata.status
|
||||
evo.comment = 'hello world %d' % i
|
||||
formdata.evolution.append(evo)
|
||||
|
@ -930,24 +930,19 @@ def test_sql_table_select_datetime(pub):
|
|||
data_class = test_formdef.data_class(mode='sql')
|
||||
assert data_class.count() == 0
|
||||
|
||||
d = datetime.datetime(2014, 1, 1)
|
||||
d = make_aware(datetime.datetime(2014, 1, 1))
|
||||
for i in range(50):
|
||||
t = data_class()
|
||||
t.receipt_time = (d + datetime.timedelta(days=i)).timetuple()
|
||||
t.receipt_time = d + datetime.timedelta(days=i)
|
||||
t.store()
|
||||
|
||||
assert data_class.count() == 50
|
||||
assert len(data_class.select()) == 50
|
||||
|
||||
assert len(data_class.select(lambda x: x.receipt_time == d.timetuple())) == 1
|
||||
assert len(data_class.select([st.Equal('receipt_time', d.timetuple())])) == 1
|
||||
assert (
|
||||
len(data_class.select([st.Less('receipt_time', (d + datetime.timedelta(days=20)).timetuple())])) == 20
|
||||
)
|
||||
assert (
|
||||
len(data_class.select([st.Greater('receipt_time', (d + datetime.timedelta(days=20)).timetuple())]))
|
||||
== 29
|
||||
)
|
||||
assert len(data_class.select(lambda x: x.receipt_time == d)) == 1
|
||||
assert len(data_class.select([st.Equal('receipt_time', d)])) == 1
|
||||
assert len(data_class.select([st.Less('receipt_time', d + datetime.timedelta(days=20))])) == 20
|
||||
assert len(data_class.select([st.Greater('receipt_time', d + datetime.timedelta(days=20))])) == 29
|
||||
assert len(data_class.select([st.Equal('receipt_time', datetime.date(1900, 1, 1).timetuple())])) == 0
|
||||
assert len(data_class.select([st.Equal('receipt_time', datetime.date(1, 1, 1))])) == 0
|
||||
assert len(data_class.select([st.Greater('receipt_time', datetime.date(1, 1, 1))])) == 50
|
||||
|
@ -1611,7 +1606,7 @@ def test_views_fts(pub):
|
|||
def test_select_any_formdata(pub):
|
||||
drop_formdef_tables()
|
||||
|
||||
now = datetime.datetime.now()
|
||||
now = localtime()
|
||||
|
||||
cnt = 0
|
||||
for i in range(5):
|
||||
|
@ -1626,7 +1621,7 @@ def test_select_any_formdata(pub):
|
|||
formdata.just_created()
|
||||
formdata.user_id = '%s' % ((i + j) % 11)
|
||||
# set receipt_time to make sure all entries are unique.
|
||||
formdata.receipt_time = (now + datetime.timedelta(seconds=cnt)).timetuple()
|
||||
formdata.receipt_time = now + datetime.timedelta(seconds=cnt)
|
||||
formdata.status = ['wf-new', 'wf-accepted', 'wf-rejected', 'wf-finished'][(i + j) % 4]
|
||||
if j < 5:
|
||||
formdata.submission_channel = 'mail'
|
||||
|
@ -1670,7 +1665,7 @@ def test_select_any_formdata(pub):
|
|||
def test_load_all_evolutions_on_any_formdata(pub):
|
||||
drop_formdef_tables()
|
||||
|
||||
now = datetime.datetime.now()
|
||||
now = localtime()
|
||||
|
||||
cnt = 0
|
||||
for i in range(5):
|
||||
|
@ -1685,7 +1680,7 @@ def test_load_all_evolutions_on_any_formdata(pub):
|
|||
formdata.just_created()
|
||||
formdata.user_id = '%s' % ((i + j) % 11)
|
||||
# set receipt_time to make sure all entries are unique.
|
||||
formdata.receipt_time = (now + datetime.timedelta(seconds=cnt)).timetuple()
|
||||
formdata.receipt_time = now + datetime.timedelta(seconds=cnt)
|
||||
formdata.status = ['wf-new', 'wf-accepted', 'wf-rejected', 'wf-finished'][(i + j) % 4]
|
||||
formdata.store()
|
||||
cnt += 1
|
||||
|
@ -1697,6 +1692,26 @@ def test_load_all_evolutions_on_any_formdata(pub):
|
|||
assert len([x for x in objects if x._evolution is not None]) == 100
|
||||
|
||||
|
||||
def test_store_on_any_formdata(pub):
|
||||
drop_formdef_tables()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test any store'
|
||||
formdef.fields = []
|
||||
formdef.store()
|
||||
|
||||
data_class = formdef.data_class(mode='sql')
|
||||
formdata = data_class()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = localtime()
|
||||
formdata.store()
|
||||
|
||||
objects = sql.AnyFormData.select()
|
||||
assert len(objects) == 1
|
||||
with pytest.raises(TypeError):
|
||||
objects[0].store()
|
||||
|
||||
|
||||
def test_geoloc_in_global_view(pub):
|
||||
drop_formdef_tables()
|
||||
|
||||
|
@ -1849,8 +1864,8 @@ def test_last_update_time(pub):
|
|||
formdata1.just_created()
|
||||
formdata1.evolution[0].comment = 'comment'
|
||||
formdata1.jump_status('st1') # will add another evolution entry
|
||||
formdata1.evolution[0].time = datetime.datetime(2015, 1, 1, 0, 0, 0).timetuple()
|
||||
formdata1.evolution[1].time = datetime.datetime(2015, 1, 2, 0, 0, 0).timetuple()
|
||||
formdata1.evolution[0].time = make_aware(datetime.datetime(2015, 1, 1, 0, 0, 0))
|
||||
formdata1.evolution[1].time = make_aware(datetime.datetime(2015, 1, 2, 0, 0, 0))
|
||||
formdata1.store()
|
||||
|
||||
formdata2 = data_class()
|
||||
|
@ -1858,8 +1873,8 @@ def test_last_update_time(pub):
|
|||
formdata2.just_created()
|
||||
formdata2.evolution[0].comment = 'comment'
|
||||
formdata2.jump_status('st1') # will add another evolution entry
|
||||
formdata2.evolution[0].time = datetime.datetime(2015, 1, 3, 0, 0, 0).timetuple()
|
||||
formdata2.evolution[1].time = datetime.datetime(2015, 1, 4, 0, 0, 0).timetuple()
|
||||
formdata2.evolution[0].time = make_aware(datetime.datetime(2015, 1, 3, 0, 0, 0))
|
||||
formdata2.evolution[1].time = make_aware(datetime.datetime(2015, 1, 4, 0, 0, 0))
|
||||
formdata2.store()
|
||||
|
||||
cur.execute('''SELECT COUNT(*) FROM wcs_all_forms''')
|
||||
|
@ -2167,7 +2182,7 @@ def test_migration_30_anonymize_evo_who(pub):
|
|||
formdata.anonymised = datetime.datetime.now()
|
||||
evo = Evolution(formdata)
|
||||
evo.who = user.id
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
formdata.evolution.append(evo)
|
||||
formdata.store()
|
||||
|
||||
|
@ -2436,6 +2451,35 @@ def test_migration_86_card_uuid(pub):
|
|||
cur.close()
|
||||
|
||||
|
||||
def test_migration_formdata_page_id(pub):
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'tests migration formdata page_id'
|
||||
formdef.fields = []
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.page_id = 'xxx'
|
||||
formdata.store()
|
||||
|
||||
assert formdef.data_class().get(formdata.id).page_id == 'xxx'
|
||||
|
||||
conn, cur = sql.get_connection_and_cursor()
|
||||
cur.execute('UPDATE wcs_meta SET value = 100 WHERE key = %s', ('sql_level',))
|
||||
|
||||
# drop page_id column
|
||||
cur.execute('ALTER TABLE %s DROP COLUMN page_id' % sql.get_formdef_table_name(formdef))
|
||||
assert not column_exists_in_table(cur, sql.get_formdef_table_name(formdef), 'page_id')
|
||||
|
||||
sql.migrate()
|
||||
|
||||
assert column_exists_in_table(cur, sql.get_formdef_table_name(formdef), 'page_id')
|
||||
assert migration_level(cur) >= 101
|
||||
|
||||
conn.commit()
|
||||
cur.close()
|
||||
|
||||
|
||||
def test_logged_error_store_without_integrity_error(pub, sql_queries):
|
||||
sql.LoggedError.record('there was an error')
|
||||
|
||||
|
@ -2698,24 +2742,6 @@ def test_python_datasource_migration(pub):
|
|||
assert sql.is_reindex_needed('python_ds_migration', conn=conn, cur=cur) is False
|
||||
|
||||
|
||||
def test_sql_testdef_unicity(pub):
|
||||
testdef = TestDef()
|
||||
testdef.slug = 'test-1'
|
||||
testdef.object_type = 'formdef'
|
||||
testdef.object_id = '1'
|
||||
testdef.store()
|
||||
|
||||
# same slug, different object_id
|
||||
testdef.id = None
|
||||
testdef.object_id = '2'
|
||||
testdef.store()
|
||||
|
||||
# same slug, object_id and object_type
|
||||
testdef.id = None
|
||||
with pytest.raises(psycopg2.errors.UniqueViolation):
|
||||
testdef.store()
|
||||
|
||||
|
||||
def test_form_tokens_migration(pub):
|
||||
conn, cur = sql.get_connection_and_cursor()
|
||||
cur.execute('UPDATE wcs_meta SET value = 70 WHERE key = %s', ('sql_level',))
|
||||
|
@ -2760,7 +2786,7 @@ def test_workflow_traces_initial_migration(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.evolution[-1].time = (datetime.datetime.now() - datetime.timedelta(seconds=11)).timetuple()
|
||||
formdata.evolution[-1].time = localtime() - datetime.timedelta(seconds=11)
|
||||
action_part = ActionsTracingEvolutionPart()
|
||||
action_part.event = 'frontoffice-created'
|
||||
action_part.actions = [
|
||||
|
@ -2769,7 +2795,7 @@ def test_workflow_traces_initial_migration(pub):
|
|||
]
|
||||
formdata.evolution[-1].add_part(action_part)
|
||||
formdata.evolution.append(Evolution(formdata))
|
||||
formdata.evolution[-1].time = (datetime.datetime.now() - datetime.timedelta(seconds=8)).timetuple()
|
||||
formdata.evolution[-1].time = localtime() - datetime.timedelta(seconds=8)
|
||||
action_part = ActionsTracingEvolutionPart()
|
||||
action_part.event = 'timeout-jump'
|
||||
action_part.event_args = ('xxx',)
|
||||
|
@ -2778,7 +2804,7 @@ def test_workflow_traces_initial_migration(pub):
|
|||
]
|
||||
formdata.evolution[-1].add_part(action_part)
|
||||
formdata.evolution.append(Evolution(formdata))
|
||||
formdata.evolution[-1].time = (datetime.datetime.now() - datetime.timedelta(seconds=6)).timetuple()
|
||||
formdata.evolution[-1].time = localtime() - datetime.timedelta(seconds=6)
|
||||
action_part = ActionsTracingEvolutionPart()
|
||||
action_part.event = 'global-action-timeout'
|
||||
action_part.event_args = ('xxx2', 'xxx3')
|
||||
|
@ -2787,7 +2813,7 @@ def test_workflow_traces_initial_migration(pub):
|
|||
]
|
||||
formdata.evolution[-1].add_part(action_part)
|
||||
formdata.evolution.append(Evolution(formdata))
|
||||
formdata.evolution[-1].time = (datetime.datetime.now() - datetime.timedelta(seconds=4)).timetuple()
|
||||
formdata.evolution[-1].time = localtime() - datetime.timedelta(seconds=4)
|
||||
action_part = ActionsTracingEvolutionPart()
|
||||
action_part.event = 'global-api-trigger'
|
||||
action_part.event_args = ('xxx2',)
|
||||
|
@ -2800,7 +2826,7 @@ def test_workflow_traces_initial_migration(pub):
|
|||
|
||||
formdata2 = formdef.data_class()()
|
||||
formdata2.just_created()
|
||||
formdata2.evolution[-1].time = (datetime.datetime.now() - datetime.timedelta(seconds=2)).timetuple()
|
||||
formdata2.evolution[-1].time = localtime() - datetime.timedelta(seconds=2)
|
||||
action_part = ActionsTracingEvolutionPart()
|
||||
action_part.event = 'workflow-created'
|
||||
action_part.external_workflow_id = '1'
|
||||
|
|
|
@ -2,6 +2,8 @@ import datetime
|
|||
import html
|
||||
import os
|
||||
import string
|
||||
import subprocess
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from django.test import override_settings
|
||||
|
@ -34,6 +36,8 @@ def pub():
|
|||
pub.substitutions.feed(pub)
|
||||
req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
|
||||
pub.set_app_dir(req)
|
||||
pub._set_request(req)
|
||||
req.session = pub.session_manager.session_class(id='1')
|
||||
pub.site_options.set('options', 'working_day_calendar', '')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
|
@ -449,6 +453,8 @@ def test_decimal_templatetag(pub):
|
|||
assert tmpl.render({'plop': 12345.678}) == '12345.678'
|
||||
assert tmpl.render({'plop': None}) == '0'
|
||||
assert tmpl.render({'plop': 0}) == '0'
|
||||
assert tmpl.render({'plop': ['foo', 'bar']}) == '0'
|
||||
assert tmpl.render({'plop': ['a', 'b', 'c']}) == '0'
|
||||
|
||||
tmpl = Template('{{ plop|decimal:3 }}')
|
||||
assert tmpl.render({'plop': '3.14'}) == '3.140'
|
||||
|
@ -1632,10 +1638,25 @@ def test_json_dumps(pub):
|
|||
)
|
||||
|
||||
|
||||
def test_empty_make_public_url(pub):
|
||||
def test_make_public_url(pub):
|
||||
# empty value
|
||||
context = {'value': None}
|
||||
assert Template('{% make_public_url url=value %}').render(context) == ''
|
||||
|
||||
# lazy value
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'lazy'
|
||||
formdef.fields = [fields.StringField(id='0', label='string', varname='foo')]
|
||||
formdef.store()
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'0': 'https://example.net'}
|
||||
formdata.store()
|
||||
context = CompatibilityNamesDict({'form': LazyFormData(formdata)})
|
||||
assert (
|
||||
Template('{% make_public_url url=form_var_foo %}').render(context).startswith('/api/sign-url-token/')
|
||||
)
|
||||
|
||||
|
||||
def test_with_auth(pub):
|
||||
context = {'service_url': 'https://www.example.net/api/whatever?x=y'}
|
||||
|
@ -1649,3 +1670,152 @@ def test_with_auth(pub):
|
|||
Template('{{ service_url|with_auth:"username:password" }}').render(context)
|
||||
== 'https://username:password@www.example.net/api/whatever?x=y'
|
||||
)
|
||||
|
||||
|
||||
def test_check_no_duplicates(pub):
|
||||
pub.loggederror_class.wipe()
|
||||
context = {'value1': ['a', 'b', 'c'], 'value2': ['a', 'a', 'b', 'c'], 'value3': None, 'value4': '12'}
|
||||
assert Template('{% if value1|check_no_duplicates %}ok{% else %}nok{% endif %}').render(context) == 'ok'
|
||||
assert Template('{% if value2|check_no_duplicates %}ok{% else %}nok{% endif %}').render(context) == 'nok'
|
||||
assert Template('{% if value3|check_no_duplicates %}ok{% else %}nok{% endif %}').render(context) == 'ok'
|
||||
assert pub.loggederror_class.count() == 0
|
||||
assert Template('{% if value4|check_no_duplicates %}ok{% else %}nok{% endif %}').render(context) == 'nok'
|
||||
assert pub.loggederror_class.count() == 1
|
||||
logged_error = pub.loggederror_class.select()[0]
|
||||
assert logged_error.summary == '|check_no_duplicates not used on a list (12)'
|
||||
|
||||
|
||||
def test_details_format(pub):
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'foo-details'
|
||||
formdef.fields = [fields.StringField(id='1', label='String')]
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'1': 'foo'}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
pub.substitutions.feed(formdata)
|
||||
context = pub.substitutions.get_context_variables(mode='lazy')
|
||||
tmpl = Template('{{ form_details|details_format }}')
|
||||
pub.loggederror_class.wipe()
|
||||
assert tmpl.render(context) == ''
|
||||
logged_error = pub.loggederror_class.select()[0]
|
||||
assert logged_error.summary == '|details_format called without specifying a format'
|
||||
|
||||
tmpl = Template('{{ form_details|details_format:"xxx" }}')
|
||||
pub.loggederror_class.wipe()
|
||||
assert tmpl.render(context) == ''
|
||||
logged_error = pub.loggederror_class.select()[0]
|
||||
assert logged_error.summary == '|details_format called with unknown format (xxx)'
|
||||
|
||||
tmpl = Template('{{ form_details|details_format:"text" }}')
|
||||
pub.loggederror_class.wipe()
|
||||
assert tmpl.render(context) == 'String:\n foo'
|
||||
assert pub.loggederror_class.count() == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize('image_format', ['jpeg', 'png', 'pdf'])
|
||||
def test_convert_image_format(pub, image_format):
|
||||
with pub.complex_data():
|
||||
img = Template('{{ url|qrcode|convert_image_format:"%s" }}' % image_format).render(
|
||||
{'url': 'http://example.com/', 'allow_complex': True}
|
||||
)
|
||||
assert pub.has_cached_complex_data(img)
|
||||
value = pub.get_cached_complex_data(img)
|
||||
assert value.orig_filename == 'qrcode.%s' % image_format
|
||||
assert value.content_type == {'jpeg': 'image/jpeg', 'png': 'image/png', 'pdf': 'application/pdf'}.get(
|
||||
image_format
|
||||
)
|
||||
with value.get_file_pointer() as fp:
|
||||
if image_format in ('jpeg', 'png'):
|
||||
img = PIL.Image.open(fp)
|
||||
assert img.format == image_format.upper()
|
||||
assert img.size == (330, 330)
|
||||
assert (
|
||||
zbar_decode_qrcode(img, symbols=[ZBarSymbol.QRCODE])[0].data.decode()
|
||||
== 'http://example.com/'
|
||||
)
|
||||
else:
|
||||
assert b'%PDF-' in fp.read()[:200]
|
||||
|
||||
|
||||
def test_convert_image_format_no_name(pub):
|
||||
with pub.complex_data():
|
||||
img = Template('{{ url|qrcode|rename_file:""|convert_image_format:"jpeg" }}').render(
|
||||
{'url': 'http://example.com/', 'allow_complex': True}
|
||||
)
|
||||
assert pub.has_cached_complex_data(img)
|
||||
value = pub.get_cached_complex_data(img)
|
||||
assert value.orig_filename == 'file.jpeg'
|
||||
|
||||
|
||||
def test_convert_image_format_errors(pub):
|
||||
pub.loggederror_class.wipe()
|
||||
with pub.complex_data():
|
||||
img = Template('{{ "xxx"|convert_image_format:"gif" }}').render({'allow_complex': True})
|
||||
assert pub.has_cached_complex_data(img)
|
||||
assert pub.get_cached_complex_data(img) is None
|
||||
assert pub.loggederror_class.count() == 1
|
||||
assert (
|
||||
pub.loggederror_class.select()[0].summary
|
||||
== '|convert_image_format: unknown format (must be one of jpeg, pdf, png)'
|
||||
)
|
||||
|
||||
pub.loggederror_class.wipe()
|
||||
with pub.complex_data():
|
||||
img = Template('{{ "xxx"|convert_image_format:"jpeg" }}').render({'allow_complex': True})
|
||||
assert pub.has_cached_complex_data(img)
|
||||
assert pub.get_cached_complex_data(img) is None
|
||||
assert pub.loggederror_class.count() == 1
|
||||
assert pub.loggederror_class.select()[0].summary == '|convert_image_format: missing input'
|
||||
|
||||
pub.loggederror_class.wipe()
|
||||
with mock.patch('subprocess.run', side_effect=FileNotFoundError()):
|
||||
with pub.complex_data():
|
||||
img = Template('{{ url|qrcode|convert_image_format:"jpeg" }}').render(
|
||||
{'url': 'http://example.com/', 'allow_complex': True}
|
||||
)
|
||||
assert pub.has_cached_complex_data(img)
|
||||
assert pub.get_cached_complex_data(img) is None
|
||||
assert pub.loggederror_class.count() == 1
|
||||
assert pub.loggederror_class.select()[0].summary == '|convert_image_format: not supported'
|
||||
|
||||
pub.loggederror_class.wipe()
|
||||
with mock.patch(
|
||||
'subprocess.run', side_effect=subprocess.CalledProcessError(returncode=-1, cmd='xx', stderr=b'xxx')
|
||||
):
|
||||
with pub.complex_data():
|
||||
img = Template('{{ url|qrcode|convert_image_format:"jpeg" }}').render(
|
||||
{'url': 'http://example.com/', 'allow_complex': True}
|
||||
)
|
||||
assert pub.has_cached_complex_data(img)
|
||||
assert pub.get_cached_complex_data(img) is None
|
||||
assert pub.loggederror_class.count() == 1
|
||||
assert pub.loggederror_class.select()[0].summary == '|convert_image_format: conversion error (xxx)'
|
||||
|
||||
|
||||
def test_temporary_access_url(pub):
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'foobar'
|
||||
formdef.fields = [fields.StringField(id='1', label='Test', varname='foo')]
|
||||
formdef.store()
|
||||
|
||||
# no formdata
|
||||
context = pub.substitutions.get_context_variables(mode='lazy')
|
||||
assert Template('{% temporary_access_url %}').render(context) == ''
|
||||
|
||||
# formdata
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {'1': 'Foo Bar'}
|
||||
formdata.store()
|
||||
pub.substitutions.feed(formdata)
|
||||
context = pub.substitutions.get_context_variables(mode='lazy')
|
||||
assert Template('{% temporary_access_url %}').render(context).startswith('http://example.net/code/')
|
||||
|
||||
# removed formdata
|
||||
formdata.remove_self()
|
||||
assert Template('{% temporary_access_url %}').render(context) == ''
|
||||
|
|
|
@ -1,20 +1,23 @@
|
|||
import datetime
|
||||
import decimal
|
||||
import io
|
||||
import json
|
||||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
import responses
|
||||
from quixote import get_publisher
|
||||
from django.utils.timezone import make_aware, now
|
||||
|
||||
from wcs import fields
|
||||
from wcs import fields, workflow_tests
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.qommon.storage import Equal
|
||||
from wcs.qommon.upload_storage import PicklableUpload
|
||||
from wcs.testdef import TestDef, TestError
|
||||
from wcs.testdef import TestDef, TestDefXmlProxy, TestError, TestResult, WebserviceResponse
|
||||
from wcs.wscalls import NamedWsCall
|
||||
|
||||
from .utilities import clean_temporary_pub, create_temporary_pub
|
||||
|
@ -30,6 +33,10 @@ def pub():
|
|||
|
||||
FormDef.wipe()
|
||||
BlockDef.wipe()
|
||||
WebserviceResponse.wipe()
|
||||
NamedWsCall.wipe()
|
||||
TestResult.wipe()
|
||||
TestDef.wipe()
|
||||
return pub
|
||||
|
||||
|
||||
|
@ -37,22 +44,169 @@ def teardown_module(module):
|
|||
clean_temporary_pub()
|
||||
|
||||
|
||||
def test_testdef_slug_generation(pub):
|
||||
testdef = TestDef()
|
||||
def test_testdef_export_to_xml(pub):
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.fields = [
|
||||
fields.ItemsField(id='1', label='Test', items=['foo', 'bar', 'baz']),
|
||||
fields.BoolField(id='2', label='Check', varname='check'),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.data['1'] = ['foo', 'baz']
|
||||
formdata.data['2'] = True
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.workflow_tests.actions = [
|
||||
workflow_tests.AssertStatus(status_name='End status'),
|
||||
]
|
||||
testdef.name = 'test'
|
||||
testdef.object_type = 'formdef'
|
||||
testdef.object_id = '1'
|
||||
testdef.expected_error = 'xxx'
|
||||
testdef.store()
|
||||
assert testdef.slug == 'test'
|
||||
|
||||
testdef.slug = testdef.id = None
|
||||
testdef.store()
|
||||
assert testdef.slug == 'test-2'
|
||||
response = WebserviceResponse()
|
||||
response.testdef_id = testdef.id
|
||||
response.name = 'Fake response'
|
||||
response.store()
|
||||
|
||||
testdef.slug = testdef.id = None
|
||||
testdef.object_id = '2'
|
||||
testdef.store()
|
||||
assert testdef.slug == 'test'
|
||||
testdef_xml = ET.tostring(testdef.export_to_xml())
|
||||
TestDef.wipe()
|
||||
workflow_tests.WorkflowTests.wipe()
|
||||
WebserviceResponse.wipe()
|
||||
|
||||
testdef2 = TestDef.import_from_xml(io.BytesIO(testdef_xml), formdef)
|
||||
assert testdef2.name == 'test'
|
||||
assert testdef2.object_type == 'formdefs'
|
||||
assert testdef2.object_id == str(formdef.id)
|
||||
assert testdef2.data == {'fields': {'1': ['foo', 'baz'], '2': True}, 'user': None}
|
||||
assert testdef2.expected_error == 'xxx'
|
||||
assert testdef2.is_in_backoffice is False
|
||||
|
||||
assert len(testdef2.workflow_tests.actions) == 1
|
||||
assert testdef2.workflow_tests.actions[0].status_name == 'End status'
|
||||
|
||||
assert len(testdef2.get_webservice_responses()) == 1
|
||||
assert testdef2.get_webservice_responses()[0].name == 'Fake response'
|
||||
|
||||
# check storage of temporary object used during import is forbidden
|
||||
testdef_xml = TestDefXmlProxy()
|
||||
with pytest.raises(AssertionError):
|
||||
testdef_xml.store()
|
||||
|
||||
|
||||
def test_testdef_result_migrate_legacy_json(pub):
|
||||
test_result = TestResult()
|
||||
test_result.object_type = 'formdef'
|
||||
test_result.object_id = '1'
|
||||
test_result.timestamp = datetime.datetime(2021, 1, 1, 0, 0)
|
||||
test_result.success = False
|
||||
test_result.reason = 'xxx'
|
||||
test_result.results = [
|
||||
{
|
||||
'id': '1',
|
||||
'name': 'xxx',
|
||||
'error': 'xxx',
|
||||
'recorded_errors': ['a', 'b'],
|
||||
'missing_required_fields': ['c', 'd'],
|
||||
},
|
||||
{
|
||||
'id': '2',
|
||||
'name': 'xxx',
|
||||
'error': 'xxx',
|
||||
},
|
||||
]
|
||||
test_result.store()
|
||||
test_result.store()
|
||||
|
||||
TestResult.migrate_legacy()
|
||||
test_result = TestResult.get(test_result.id)
|
||||
assert test_result.results == [
|
||||
{
|
||||
'id': '1',
|
||||
'name': 'xxx',
|
||||
'error': 'xxx',
|
||||
'details': {
|
||||
'form_status': None,
|
||||
'recorded_errors': ['a', 'b'],
|
||||
'missing_required_fields': ['c', 'd'],
|
||||
'workflow_test_action_uuid': None,
|
||||
},
|
||||
},
|
||||
{
|
||||
'id': '2',
|
||||
'name': 'xxx',
|
||||
'error': 'xxx',
|
||||
'details': {
|
||||
'form_status': None,
|
||||
'recorded_errors': [],
|
||||
'missing_required_fields': [],
|
||||
'workflow_test_action_uuid': None,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def test_testdef_result_clean(pub, freezer):
|
||||
def make_result(formdef_id, success):
|
||||
test_result = TestResult()
|
||||
test_result.object_type = 'formdef'
|
||||
test_result.object_id = formdef_id
|
||||
test_result.timestamp = now()
|
||||
test_result.success = success
|
||||
test_result.reason = 'xxx'
|
||||
test_result.store()
|
||||
|
||||
# FormDef 1
|
||||
freezer.move_to('2024-01-25 12:00')
|
||||
for i in range(20):
|
||||
make_result(formdef_id='1', success=True)
|
||||
|
||||
# FormDef 2
|
||||
freezer.move_to('2024-01-10 12:00')
|
||||
for i in range(15):
|
||||
make_result(formdef_id='2', success=True)
|
||||
|
||||
for i in range(15):
|
||||
freezer.move_to('2024-01-15 12:%s' % i)
|
||||
make_result(formdef_id='2', success=True)
|
||||
|
||||
# FormDef 3
|
||||
freezer.move_to('2024-01-10 12:00')
|
||||
for i in range(15):
|
||||
make_result(formdef_id='3', success=False)
|
||||
|
||||
freezer.move_to('2024-01-11 12:00')
|
||||
make_result(formdef_id='3', success=True)
|
||||
|
||||
freezer.move_to('2024-01-12 12:00')
|
||||
for i in range(5):
|
||||
make_result(formdef_id='3', success=False)
|
||||
|
||||
freezer.move_to('2024-01-25 12:00')
|
||||
for i in range(10):
|
||||
make_result(formdef_id='3', success=False)
|
||||
|
||||
freezer.move_to('2024-02-01 12:00')
|
||||
TestResult.clean()
|
||||
|
||||
# no deletion for FormDef 1
|
||||
results_formdef1 = TestResult.select(clause=[Equal('object_id', '1')])
|
||||
assert len(results_formdef1) == 20
|
||||
|
||||
# 10 most recent results were kept for FormDef 2
|
||||
results_formdef2 = TestResult.select(clause=[Equal('object_id', '2')])
|
||||
assert len(results_formdef2) == 10
|
||||
assert all(x.timestamp.day == 15 for x in results_formdef2)
|
||||
|
||||
# all recently failed results were kept for FormDef 3, including last success
|
||||
results_formdef3 = TestResult.select(clause=[Equal('object_id', '3')])
|
||||
assert len(results_formdef3) == 16
|
||||
assert len([x for x in results_formdef3 if x.success]) == 1
|
||||
assert len([x for x in results_formdef3 if x.timestamp.day == 12]) == 5
|
||||
assert len([x for x in results_formdef3 if x.timestamp.day == 25]) == 10
|
||||
|
||||
|
||||
def test_testdef_create_from_formdata_boolean(pub):
|
||||
|
@ -124,7 +278,7 @@ def test_page_post_conditions(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['2'] = 'a'
|
||||
formdata.data['4'] = 'a'
|
||||
|
||||
|
@ -158,7 +312,7 @@ def test_page_post_condition_invalid(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
with pytest.raises(TestError) as excinfo:
|
||||
|
@ -183,7 +337,7 @@ def test_field_conditions(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = 'a'
|
||||
formdata.data['2'] = 'xxx'
|
||||
|
||||
|
@ -221,7 +375,7 @@ def test_field_conditions_boolean(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = False
|
||||
formdata.data['2'] = None
|
||||
|
||||
|
@ -261,7 +415,7 @@ def test_multi_page_condition(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = 'a'
|
||||
formdata.data['3'] = 'xxx'
|
||||
formdata.data['5'] = 'yyy'
|
||||
|
@ -296,7 +450,7 @@ def test_validation_string_field(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = '1'
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
|
@ -322,7 +476,7 @@ def test_validation_required_field(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.run(formdef)
|
||||
|
@ -341,14 +495,22 @@ def test_validation_item_field(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = 'foo'
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.run(formdef)
|
||||
|
||||
# no check on invalid value
|
||||
formdata.data['1'] = 'xxx'
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
with pytest.raises(TestError) as excinfo:
|
||||
testdef.run(formdef)
|
||||
assert str(excinfo.value) == 'Invalid value "xxx" for field "Test": invalid value selected'
|
||||
|
||||
# no check on invalid value for field with data source
|
||||
formdef.fields[0].data_source = {'type': 'jsonvalue', 'value': json.dumps({})}
|
||||
formdef.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.run(formdef)
|
||||
|
||||
|
@ -373,14 +535,25 @@ def test_validation_item_field_inside_block(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = {'data': [{'1': 'foo'}]}
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.run(formdef)
|
||||
|
||||
# no check on invalid value
|
||||
formdata.data['1'] = {'data': [{'1': 'xxx'}]}
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
with pytest.raises(TestError) as excinfo:
|
||||
testdef.run(formdef)
|
||||
assert (
|
||||
str(excinfo.value) == 'Empty value for field "Test" (of field "Block Data"): invalid value selected'
|
||||
)
|
||||
|
||||
# no check on invalid value for field with data source
|
||||
block.fields[0].data_source = {'type': 'jsonvalue', 'value': json.dumps({})}
|
||||
block.store()
|
||||
formdef.refresh_from_storage()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.run(formdef)
|
||||
|
||||
|
@ -406,7 +579,7 @@ def test_validation_optional_field_inside_required_block(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = {'data': [{'1': 'foo'}]}
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
|
@ -436,7 +609,7 @@ def test_item_field_display_value(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = 'foo'
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
|
@ -467,7 +640,7 @@ def test_item_field_structured_value(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data = {
|
||||
'1': '2',
|
||||
'1_raw': '2',
|
||||
|
@ -528,7 +701,7 @@ def test_item_field_structured_value_inside_block(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = {
|
||||
'data': [
|
||||
{
|
||||
|
@ -595,7 +768,7 @@ def test_item_field_card_data_source_live(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = str(carddata.id)
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
|
@ -618,7 +791,7 @@ def test_validation_items_field(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = ['foo', 'baz']
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
|
@ -645,7 +818,7 @@ def test_validation_email_field(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = 'test@entrouvert.com'
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
|
@ -671,7 +844,7 @@ def test_validation_boolean_field(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = False
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
|
@ -696,7 +869,7 @@ def test_validation_date_field(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = time.strptime('2022-07-19', '%Y-%m-%d')
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
|
@ -729,7 +902,7 @@ def test_validation_map_field(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = '1.0;2.0'
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
|
@ -752,7 +925,7 @@ def test_validation_file_field(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
|
||||
upload = PicklableUpload('test.pdf', 'application/pdf', 'ascii')
|
||||
upload.receive([b'first line', b'second line'])
|
||||
|
@ -815,7 +988,7 @@ def test_validation_block_field(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = {'data': [{'1': 'b'}, {'1': 'a'}]}
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
|
@ -866,7 +1039,7 @@ def test_computed_field_support(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = 'zzz'
|
||||
formdata.data['3'] = 'hop'
|
||||
|
||||
|
@ -906,13 +1079,13 @@ def test_computed_field_support_complex_data(pub):
|
|||
|
||||
submitted_formdata = formdef.data_class()()
|
||||
submitted_formdata.just_created()
|
||||
submitted_formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
submitted_formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
submitted_formdata.data['2'] = ['a', 'bc']
|
||||
submitted_formdata.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
with pytest.raises(TestError) as excinfo:
|
||||
|
@ -956,11 +1129,65 @@ def test_computed_field_support_webservice(pub, http_requests):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.store()
|
||||
testdef.run(formdef)
|
||||
|
||||
assert len(testdef.sent_requests) == 1
|
||||
assert testdef.sent_requests[0]['method'] == 'GET'
|
||||
assert testdef.sent_requests[0]['url'] == 'http://remote.example.net/json'
|
||||
|
||||
response = WebserviceResponse()
|
||||
response.testdef_id = testdef.id
|
||||
response.name = 'Fake response'
|
||||
response.url = 'http://remote.example.net/json'
|
||||
response.payload = '{"foo": "bar"}'
|
||||
response.store()
|
||||
|
||||
testdef.run(formdef)
|
||||
|
||||
assert len(testdef.sent_requests) == 1
|
||||
assert testdef.sent_requests[0]['url'] == 'http://remote.example.net/json'
|
||||
assert testdef.sent_requests[0]['webservice_response_id'] == response.id
|
||||
|
||||
response.payload = '{"foo": "baz"}'
|
||||
response.store()
|
||||
|
||||
with pytest.raises(TestError) as excinfo:
|
||||
testdef.run(formdef)
|
||||
assert str(excinfo.value) == 'Page 1 post condition was not met (form_var_computed_foo == "bar").'
|
||||
assert len(testdef.sent_requests) == 1
|
||||
assert testdef.sent_requests[0]['url'] == 'http://remote.example.net/json'
|
||||
assert testdef.sent_requests[0]['webservice_response_id'] == response.id
|
||||
|
||||
response.url = 'http://example.com/json'
|
||||
response.store()
|
||||
|
||||
testdef.run(formdef)
|
||||
assert len(testdef.sent_requests) == 1
|
||||
assert testdef.sent_requests[0]['url'] == 'http://remote.example.net/json'
|
||||
assert testdef.sent_requests[0]['webservice_response_id'] is None
|
||||
|
||||
response.url = None
|
||||
response.store()
|
||||
|
||||
testdef.run(formdef)
|
||||
assert len(testdef.sent_requests) == 1
|
||||
assert testdef.sent_requests[0]['url'] == 'http://remote.example.net/json'
|
||||
assert testdef.sent_requests[0]['webservice_response_id'] is None
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
with mock.patch('wcs.testdef.MockWebserviceResponseAdapter._send', side_effect=KeyError('missing key')):
|
||||
with pytest.raises(TestError):
|
||||
testdef.run(formdef)
|
||||
|
||||
assert len(testdef.sent_requests) == 0
|
||||
assert testdef.recorded_errors == [
|
||||
"Unexpected error when mocking webservice call for url http://remote.example.net/json: 'missing key'."
|
||||
]
|
||||
|
||||
|
||||
def test_computed_field_value_too_long(pub):
|
||||
formdef = FormDef()
|
||||
|
@ -987,7 +1214,7 @@ def test_computed_field_value_too_long(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
with pytest.raises(TestError):
|
||||
|
@ -1000,6 +1227,50 @@ def test_computed_field_value_too_long(pub):
|
|||
testdef.run(formdef)
|
||||
|
||||
|
||||
def test_computed_field_forms_template_access(pub):
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.fields = [
|
||||
fields.PageField(
|
||||
id='0',
|
||||
label='1st page',
|
||||
post_conditions=[
|
||||
{
|
||||
'condition': {'type': 'django', 'value': 'form_var_computed == 1'},
|
||||
'error_message': 'Not enough chars.',
|
||||
}
|
||||
],
|
||||
),
|
||||
fields.ComputedField(
|
||||
id='1',
|
||||
label='Computed',
|
||||
varname='computed',
|
||||
value_template='{{ forms|objects:"test-title"|count }}',
|
||||
freeze_on_initial_value=True,
|
||||
),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.run(formdef)
|
||||
|
||||
formdef.fields[
|
||||
1
|
||||
].value_template = '{{ forms|objects:"test-title"|filter_by:"unknown"|filter_value:"xxx"|count }}'
|
||||
formdef.store()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
with pytest.raises(TestError) as excinfo:
|
||||
testdef.run(formdef)
|
||||
assert str(excinfo.value) == 'Page 1 post condition was not met (form_var_computed == 1).'
|
||||
assert testdef.recorded_errors == ['Invalid filter "unknown"']
|
||||
|
||||
|
||||
def test_expected_error(pub):
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
|
@ -1020,7 +1291,7 @@ def test_expected_error(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = '123456'
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
|
@ -1065,7 +1336,7 @@ def test_expected_error_conditional_field(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
formdata.data['1'] = 'a'
|
||||
formdata.data['2'] = 'b'
|
||||
|
||||
|
@ -1092,34 +1363,6 @@ def test_expected_error_conditional_field(pub):
|
|||
)
|
||||
|
||||
|
||||
def test_record_error_raises_exception(pub, monkeypatch):
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.fields = [
|
||||
fields.StringField(id='1', label='String field digits'),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.data['1'] = '1'
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.run(formdef)
|
||||
|
||||
def record_error():
|
||||
try:
|
||||
1 / 0
|
||||
except ZeroDivisionError as e:
|
||||
get_publisher().record_error('Error', formdef=formdef, exception=e)
|
||||
|
||||
with pytest.raises(ZeroDivisionError) as excinfo:
|
||||
with mock.patch('wcs.qommon.form.StringWidget.parse', side_effect=record_error):
|
||||
testdef.run(formdef)
|
||||
assert str(excinfo.value) == 'division by zero'
|
||||
|
||||
|
||||
def test_is_in_backoffice(pub):
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
|
@ -1139,7 +1382,7 @@ def test_is_in_backoffice(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
|
||||
formdata.receipt_time = make_aware(datetime.datetime(2021, 1, 1, 0, 0))
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.run(formdef)
|
||||
|
@ -1152,3 +1395,89 @@ def test_is_in_backoffice(pub):
|
|||
|
||||
testdef.is_in_backoffice = False
|
||||
testdef.run(formdef)
|
||||
|
||||
|
||||
def test_webservice_response_match_request(pub, http_requests):
|
||||
formdef = FormDef()
|
||||
formdef.name = 'test title'
|
||||
formdef.fields = [
|
||||
fields.ComputedField(
|
||||
id='1',
|
||||
label='Computed',
|
||||
varname='computed',
|
||||
value_template='{{ webservice.hello_world }}',
|
||||
freeze_on_initial_value=True,
|
||||
),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
|
||||
testdef = TestDef.create_from_formdata(formdef, formdata)
|
||||
testdef.store()
|
||||
|
||||
wscall = NamedWsCall()
|
||||
wscall.name = 'Hello world'
|
||||
wscall.request = {
|
||||
'url': 'http://remote.example.net/json',
|
||||
'method': 'POST',
|
||||
'qs_data': {'foo': 'bar'},
|
||||
'post_data': {'foo2': 'bar2'},
|
||||
}
|
||||
wscall.store()
|
||||
|
||||
response = WebserviceResponse()
|
||||
response.testdef_id = testdef.id
|
||||
response.name = 'Fake response'
|
||||
response.url = 'http://remote.example.net/json'
|
||||
response.payload = '{}'
|
||||
response.store()
|
||||
|
||||
testdef.run(formdef)
|
||||
assert testdef.sent_requests[0]['webservice_response_id'] == response.id
|
||||
|
||||
# method restriction
|
||||
response.method = 'GET'
|
||||
response.store()
|
||||
|
||||
testdef.run(formdef)
|
||||
assert testdef.sent_requests[0]['webservice_response_id'] is None
|
||||
|
||||
response.method = 'POST'
|
||||
response.store()
|
||||
|
||||
testdef.run(formdef)
|
||||
assert testdef.sent_requests[0]['webservice_response_id'] == response.id
|
||||
|
||||
# query string restriction
|
||||
response.qs_data = {
|
||||
'foo': 'bar',
|
||||
'xxx': 'yyy',
|
||||
}
|
||||
response.store()
|
||||
|
||||
testdef.run(formdef)
|
||||
assert testdef.sent_requests[0]['webservice_response_id'] is None
|
||||
|
||||
del response.qs_data['xxx']
|
||||
response.store()
|
||||
|
||||
testdef.run(formdef)
|
||||
assert testdef.sent_requests[0]['webservice_response_id'] == response.id
|
||||
|
||||
# post data restriction
|
||||
response.post_data = {
|
||||
'foo2': 'bar2',
|
||||
'xxx': 'yyy',
|
||||
}
|
||||
response.store()
|
||||
|
||||
testdef.run(formdef)
|
||||
assert testdef.sent_requests[0]['webservice_response_id'] is None
|
||||
|
||||
del response.post_data['xxx']
|
||||
response.store()
|
||||
|
||||
testdef.run(formdef)
|
||||
assert testdef.sent_requests[0]['webservice_response_id'] == response.id
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import datetime
|
||||
import json
|
||||
import time
|
||||
|
||||
import pytest
|
||||
import responses
|
||||
from django.core.management import call_command
|
||||
from django.utils.timezone import localtime
|
||||
|
||||
from wcs import fields
|
||||
from wcs.carddef import CardDef
|
||||
|
@ -305,10 +305,10 @@ def test_clean_deleted_users(pub):
|
|||
formdata1 = data_class()
|
||||
formdata1.user_id = user1.id
|
||||
evo = Evolution(formdata=formdata1)
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
evo.who = user4.id
|
||||
evo2 = Evolution(formdata=formdata1)
|
||||
evo2.time = time.localtime()
|
||||
evo2.time = localtime()
|
||||
evo2.who = '_submitter'
|
||||
formdata1.evolution = [evo, evo2]
|
||||
formdata1.workflow_roles = {'_received': '_user:%s' % user5.id}
|
||||
|
|
|
@ -595,7 +595,7 @@ def test_wysiwygwidget_img():
|
|||
def test_mini_rich_text_widget():
|
||||
widget = MiniRichTextWidget('test')
|
||||
form = MockHtmlForm(widget)
|
||||
assert 'data-godo-schema="basic"' in form.as_html
|
||||
assert PyQuery(form.as_html)('godo-editor[schema=basic]')
|
||||
|
||||
|
||||
def test_mini_rich_text_widget_maxlength():
|
||||
|
@ -613,7 +613,7 @@ def test_mini_rich_text_widget_maxlength():
|
|||
def test_rich_text_widget():
|
||||
widget = RichTextWidget('test')
|
||||
form = MockHtmlForm(widget)
|
||||
assert 'data-godo-schema="full"' in form.as_html
|
||||
assert PyQuery(form.as_html)('godo-editor[schema=full]')
|
||||
|
||||
|
||||
def test_select_hint_widget():
|
||||
|
@ -1658,6 +1658,13 @@ def test_numeric_widget():
|
|||
assert widget.has_error()
|
||||
assert widget.get_error() == 'You should enter digits only, for example: 123.'
|
||||
|
||||
# existing invalid value
|
||||
widget = NumericWidget('test', max_value=10)
|
||||
widget.set_value('01.02.03')
|
||||
mock_form_submission(req, widget, {'test': '01.02.03'})
|
||||
assert widget.has_error()
|
||||
assert widget.get_error() == 'You should enter a number, for example: 123.'
|
||||
|
||||
|
||||
def test_css_classes_widget():
|
||||
for value, result, has_error in (
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -241,12 +241,12 @@ def test_webservice_on_error(http_requests, emails, notify_on_errors, record_on_
|
|||
'500': '500 Internal Server Error',
|
||||
'json-err0': None,
|
||||
'json-err0int': None,
|
||||
'json-err1': 'err: 1',
|
||||
'json-err1int': 'err: 1',
|
||||
'json-err1-with-desc': 'err: 1, err_desc: :(',
|
||||
'json-errstr': 'err: bug',
|
||||
'json-errheader1': 'err: 1',
|
||||
'json-errheaderstr': 'err: bug',
|
||||
'json-err1': None,
|
||||
'json-err1int': None,
|
||||
'json-err1-with-desc': None,
|
||||
'json-errstr': None,
|
||||
'json-errheader1': None,
|
||||
'json-errheaderstr': None,
|
||||
}
|
||||
wscall.request = {'url': 'http://remote.example.net/%s' % url_part}
|
||||
wscall.store()
|
||||
|
|
|
@ -189,6 +189,7 @@ def create_temporary_pub(pickle_mode=False, lazy_mode=False):
|
|||
sql.Application.do_table()
|
||||
sql.ApplicationElement.do_table()
|
||||
sql.SearchableFormDef.do_table()
|
||||
sql.TranslatableMessage.do_table()
|
||||
sql.init_global_table()
|
||||
|
||||
conn.close()
|
||||
|
|
|
@ -6,6 +6,7 @@ from unittest import mock
|
|||
|
||||
import pytest
|
||||
import responses
|
||||
from django.utils.timezone import localtime
|
||||
from quixote import cleanup, get_publisher, get_response
|
||||
|
||||
from wcs import sessions, sql
|
||||
|
@ -616,7 +617,7 @@ def test_anonymise(pub):
|
|||
formdata.submission_context = {'foo': 'bar'}
|
||||
formdata.store()
|
||||
evo = Evolution(formdata) # add a new evolution
|
||||
evo.time = time.localtime()
|
||||
evo.time = localtime()
|
||||
evo.status = formdata.status
|
||||
evo.who = 42
|
||||
evo.parts = [AttachmentEvolutionPart('hello.txt', fp=io.BytesIO(b'hello world'), varname='testfile')]
|
||||
|
@ -1331,7 +1332,7 @@ def test_global_timeouts(pub, formdef_class):
|
|||
pub.apply_global_action_timeouts()
|
||||
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'green'
|
||||
|
||||
formdata1.receipt_time = time.localtime(time.time() - 3 * 86400)
|
||||
formdata1.receipt_time = localtime() - datetime.timedelta(days=3)
|
||||
formdata1.store()
|
||||
pub.apply_global_action_timeouts()
|
||||
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'yellow'
|
||||
|
@ -1358,7 +1359,7 @@ def test_global_timeouts(pub, formdef_class):
|
|||
pub.apply_global_action_timeouts()
|
||||
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'green'
|
||||
|
||||
formdata1.evolution[-1].time = time.localtime(time.time() - 3 * 86400)
|
||||
formdata1.evolution[-1].time = localtime() - datetime.timedelta(days=3)
|
||||
formdata1.store()
|
||||
pub.apply_global_action_timeouts()
|
||||
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'yellow'
|
||||
|
@ -1368,7 +1369,7 @@ def test_global_timeouts(pub, formdef_class):
|
|||
# bad (obsolete) status: do nothing
|
||||
trigger.anchor_status_first = 'wf-foobar'
|
||||
workflow.store()
|
||||
formdata1.evolution[-1].time = time.localtime(time.time() - 3 * 86400)
|
||||
formdata1.evolution[-1].time = localtime() - datetime.timedelta(days=3)
|
||||
formdata1.store()
|
||||
pub.apply_global_action_timeouts()
|
||||
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'green'
|
||||
|
@ -1378,18 +1379,18 @@ def test_global_timeouts(pub, formdef_class):
|
|||
trigger.anchor_status_latest = None
|
||||
workflow.store()
|
||||
|
||||
formdata1.evolution[-1].time = time.localtime()
|
||||
formdata1.evolution[-1].time = localtime()
|
||||
formdata1.store()
|
||||
formdata1.jump_status('new')
|
||||
formdata1.evolution[-1].time = time.localtime(time.time() - 7 * 86400)
|
||||
formdata1.evolution[-1].time = localtime() - datetime.timedelta(days=7)
|
||||
formdata1.jump_status('accepted')
|
||||
formdata1.jump_status('new')
|
||||
formdata1.evolution[-1].time = time.localtime(time.time() - 1 * 86400)
|
||||
formdata1.evolution[-1].time = localtime() - datetime.timedelta(days=1)
|
||||
|
||||
pub.apply_global_action_timeouts()
|
||||
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'green'
|
||||
|
||||
formdata1.evolution[-1].time = time.localtime(time.time() - 4 * 86400)
|
||||
formdata1.evolution[-1].time = localtime() - datetime.timedelta(days=4)
|
||||
formdata1.store()
|
||||
pub.apply_global_action_timeouts()
|
||||
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'yellow'
|
||||
|
@ -1418,7 +1419,7 @@ def test_global_timeouts(pub, formdef_class):
|
|||
|
||||
# check trigger is not run on finalized formdata
|
||||
formdata1.jump_status('finished')
|
||||
formdata1.evolution[-1].time = time.localtime(time.time() - 4 * 86400)
|
||||
formdata1.evolution[-1].time = localtime() - datetime.timedelta(days=4)
|
||||
formdata1.store()
|
||||
trigger.anchor = 'creation'
|
||||
workflow.store()
|
||||
|
@ -1430,7 +1431,7 @@ def test_global_timeouts(pub, formdef_class):
|
|||
# endpoint
|
||||
formdata1.jump_status('finished')
|
||||
formdata1.evolution[-1].last_jump_datetime = None
|
||||
formdata1.evolution[-1].time = time.localtime(time.time() - 4 * 86400)
|
||||
formdata1.evolution[-1].time = localtime() - datetime.timedelta(days=4)
|
||||
formdata1.store()
|
||||
trigger.anchor = 'latest-arrival'
|
||||
trigger.anchor_status_latest = 'wf-finished'
|
||||
|
@ -1451,7 +1452,7 @@ def test_global_timeouts(pub, formdef_class):
|
|||
# use python expression as anchor
|
||||
# timestamp
|
||||
formdata1.jump_status('new')
|
||||
formdata1.evolution[-1].time = time.localtime(time.time() - 4 * 86400)
|
||||
formdata1.evolution[-1].time = localtime() - datetime.timedelta(days=4)
|
||||
formdata1.evolution[-1].last_jump_datetime = None
|
||||
formdata1.store()
|
||||
|
||||
|
@ -1572,7 +1573,7 @@ def test_global_timeouts(pub, formdef_class):
|
|||
# * invalid value
|
||||
pub.loggederror_class.wipe()
|
||||
formdata1.jump_status('accepted')
|
||||
formdata1.evolution[-1].time = time.localtime(time.time() - 1 * 86400)
|
||||
formdata1.evolution[-1].time = localtime() - datetime.timedelta(days=1)
|
||||
formdata1.store()
|
||||
pub.apply_global_action_timeouts()
|
||||
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'green'
|
||||
|
@ -1597,7 +1598,7 @@ def test_global_timeouts(pub, formdef_class):
|
|||
assert pub.loggederror_class.count() == 0
|
||||
|
||||
# * ok value, and timeout is triggered
|
||||
formdata1.evolution[-1].time = time.localtime(time.time() - 4 * 86400)
|
||||
formdata1.evolution[-1].time = localtime() - datetime.timedelta(days=4)
|
||||
formdata1.store()
|
||||
pub.apply_global_action_timeouts()
|
||||
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'yellow'
|
||||
|
@ -1651,7 +1652,7 @@ def test_global_timeouts_finalized(pub, sql_queries, timeout):
|
|||
formdata1.just_created()
|
||||
formdata1.store()
|
||||
formdata1.jump_status('finished')
|
||||
formdata1.evolution[-1].time = time.localtime(time.time() - 4 * 86400)
|
||||
formdata1.evolution[-1].time = localtime() - datetime.timedelta(days=4)
|
||||
formdata1.store()
|
||||
|
||||
formdata2 = formdef.data_class()()
|
||||
|
@ -1659,7 +1660,7 @@ def test_global_timeouts_finalized(pub, sql_queries, timeout):
|
|||
formdata2.just_created()
|
||||
formdata2.store()
|
||||
formdata2.jump_status('finished')
|
||||
formdata2.evolution[-1].time = time.localtime(time.time() - 1 * 86400)
|
||||
formdata2.evolution[-1].time = localtime() - datetime.timedelta(days=1)
|
||||
formdata2.store()
|
||||
|
||||
formdef2 = FormDef()
|
||||
|
@ -1675,7 +1676,7 @@ def test_global_timeouts_finalized(pub, sql_queries, timeout):
|
|||
formdata3.just_created()
|
||||
formdata3.store()
|
||||
formdata3.jump_status('finished')
|
||||
formdata3.evolution[-1].time = time.localtime(time.time() - 6 * 86400)
|
||||
formdata3.evolution[-1].time = localtime() - datetime.timedelta(days=6)
|
||||
formdata3.store()
|
||||
|
||||
formdata4 = formdef2.data_class()()
|
||||
|
@ -1683,7 +1684,7 @@ def test_global_timeouts_finalized(pub, sql_queries, timeout):
|
|||
formdata4.just_created()
|
||||
formdata4.store()
|
||||
formdata4.jump_status('finished')
|
||||
formdata4.evolution[-1].time = time.localtime(time.time() - 4 * 86400)
|
||||
formdata4.evolution[-1].time = localtime() - datetime.timedelta(days=4)
|
||||
formdata4.store()
|
||||
|
||||
pub.apply_global_action_timeouts()
|
||||
|
@ -1735,17 +1736,18 @@ def test_global_timeouts_latest_arrival(pub):
|
|||
|
||||
formdata1.jump_status('new')
|
||||
# enter in status 8 days ago
|
||||
formdata1.evolution[-1].time = time.localtime(time.time() - 8 * 86400)
|
||||
formdata1.evolution[-1].time = localtime() - datetime.timedelta(days=8)
|
||||
formdata1.store()
|
||||
# but get a new comment 1 day ago
|
||||
formdata1.evolution.append(Evolution(formdata1))
|
||||
formdata1.evolution[-1].time = time.localtime(time.time() - 1 * 86400)
|
||||
formdata1.evolution[-1].time = localtime() - datetime.timedelta(days=1)
|
||||
formdata1.evolution[-1].comment = 'plop'
|
||||
formdata1.store()
|
||||
pub.apply_global_action_timeouts()
|
||||
# no change
|
||||
assert formdef.data_class().get(formdata1.id).get_criticality_level_object().name == 'green'
|
||||
|
||||
formdata1.evolution[-1].time = time.localtime(time.time() - 5 * 86400)
|
||||
formdata1.evolution[-1].time = localtime() - datetime.timedelta(days=5)
|
||||
formdata1.store()
|
||||
pub.apply_global_action_timeouts()
|
||||
# change
|
||||
|
@ -1756,7 +1758,7 @@ def test_global_timeouts_latest_arrival(pub):
|
|||
formdata1.just_created()
|
||||
formdata1.store()
|
||||
formdata1.jump_status('new')
|
||||
formdata1.evolution[-1].time = time.localtime(time.time() - 5 * 86400)
|
||||
formdata1.evolution[-1].time = localtime() - datetime.timedelta(days=5)
|
||||
formdata1.store()
|
||||
formdata1.jump_status('accepted')
|
||||
formdata1.store()
|
||||
|
@ -1768,7 +1770,7 @@ def test_global_timeouts_latest_arrival(pub):
|
|||
formdata1.just_created()
|
||||
formdata1.store()
|
||||
formdata1.jump_status('new')
|
||||
formdata1.evolution[-1].time = time.localtime(time.time() - 5 * 86400)
|
||||
formdata1.evolution[-1].time = localtime() - datetime.timedelta(days=5)
|
||||
formdata1.store()
|
||||
formdata1.jump_status('accepted')
|
||||
formdata1.jump_status('finished')
|
||||
|
@ -1962,6 +1964,7 @@ def test_redirect_to_url(pub):
|
|||
|
||||
|
||||
def test_workflow_action_condition(pub):
|
||||
Workflow.wipe()
|
||||
workflow = Workflow(name='jump condition migration')
|
||||
st1 = workflow.add_status('Status1', 'st1')
|
||||
workflow.store()
|
||||
|
@ -2044,8 +2047,16 @@ def test_workflow_action_condition(pub):
|
|||
assert logged_error.summary == 'Failed to evaluate condition'
|
||||
assert logged_error.exception_class == 'NameError'
|
||||
assert logged_error.exception_message == "name 'foobar' is not defined"
|
||||
assert logged_error.expression == 'foobar == barfoo'
|
||||
assert logged_error.expression_type == 'python'
|
||||
assert logged_error.context == {
|
||||
'stack': [
|
||||
{
|
||||
'condition': 'foobar == barfoo',
|
||||
'condition_type': 'python',
|
||||
'source_label': 'Manual Jump',
|
||||
'source_url': 'http://example.net/backoffice/workflows/1/status/st1/items/_x/',
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def test_workflow_field_migration(pub):
|
||||
|
@ -2326,3 +2337,31 @@ def test_status_colour_migration(pub):
|
|||
|
||||
workflow.refresh_from_storage()
|
||||
assert workflow.possible_status[0].colour == '#FF0000'
|
||||
|
||||
|
||||
def test_visibility_migration(pub):
|
||||
workflow = Workflow(name='visibility')
|
||||
workflow.roles = {'_reveiver': 'Receiver', '_other': 'Other function'}
|
||||
st1 = workflow.add_status('st1')
|
||||
st1.visibility = ['_receiver', '_other']
|
||||
st2 = workflow.add_status('st2')
|
||||
st2.visibility = ['__hidden__']
|
||||
workflow.add_status('st3')
|
||||
workflow.store()
|
||||
|
||||
workflow.refresh_from_storage()
|
||||
assert workflow.possible_status[0].visibility == ['__restricted__']
|
||||
assert workflow.possible_status[1].visibility == ['__hidden__']
|
||||
assert not workflow.possible_status[2].visibility
|
||||
|
||||
|
||||
def test_variables_formdef_clean_prefill(pub):
|
||||
workflow = Workflow(name='variables')
|
||||
workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
|
||||
workflow.variables_formdef.fields.append(
|
||||
StringField(label='Test', default_value='123', prefill={'type': 'string', 'value': 'plop'})
|
||||
)
|
||||
workflow.store()
|
||||
|
||||
workflow = Workflow.get(id=workflow.id)
|
||||
assert not workflow.variables_formdef.fields[0].prefill
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import base64
|
||||
import copy
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
|
@ -440,6 +441,17 @@ def test_set_backoffice_field_file(http_requests, pub):
|
|||
|
||||
assert formdata.data.get('bo1') is None
|
||||
|
||||
# check |rename_file with missing filename
|
||||
formdata.data['00'] = copy.copy(upload)
|
||||
formdata.data['00'].orig_filename = None
|
||||
formdata.data['00'].base_filename = None
|
||||
pub.substitutions.feed(formdata)
|
||||
item.fields = [{'field_id': 'bo1', 'value': '{{form_var_file|rename_file:"foobar.$ext"}}'}]
|
||||
item.perform(formdata)
|
||||
|
||||
assert formdata.data['bo1'].base_filename == 'foobar'
|
||||
assert formdata.data['bo1'].get_content() == image_with_gps_data
|
||||
|
||||
# check stripping metadata
|
||||
pub.substitutions.feed(formdata)
|
||||
item.fields = [
|
||||
|
@ -806,7 +818,7 @@ def test_set_backoffice_field_card_item(pub):
|
|||
assert formdata.data.get('bo1_structured') is None
|
||||
assert pub.loggederror_class.count() == 1
|
||||
logged_error = pub.loggederror_class.select()[0]
|
||||
assert logged_error.summary.startswith('Failed to convert')
|
||||
assert logged_error.summary == "Failed to assign field (bo1): unknown card value ('xxx')"
|
||||
|
||||
# reset, and get empty value
|
||||
formdata.data = {}
|
||||
|
|
|
@ -1119,6 +1119,117 @@ def test_edit_carddata_targeting_itself(pub):
|
|||
assert carddata.status == 'wf-%s' % st2.id
|
||||
|
||||
|
||||
def test_edit_carddata_auto_targeting_custom_id(pub):
|
||||
CardDef.wipe()
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'Foo Card'
|
||||
carddef.fields = [
|
||||
StringField(id='0', label='foo', varname='foo'),
|
||||
StringField(id='1', label='slug', varname='slug'),
|
||||
]
|
||||
carddef.id_template = 'card_{{form_var_slug}}'
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
|
||||
carddata = carddef.data_class()()
|
||||
carddata.data = {'0': 'foo', '1': 'foo'}
|
||||
carddata.store()
|
||||
carddata.just_created()
|
||||
carddata.store()
|
||||
assert carddata.identifier == 'card_foo'
|
||||
|
||||
carddef2 = CardDef()
|
||||
carddef2.name = 'Bar Card'
|
||||
carddef2.fields = [
|
||||
ItemField(id='1', label='card', varname='card', data_source={'type': 'carddef:%s' % carddef.url_name})
|
||||
]
|
||||
carddef2.store()
|
||||
|
||||
card_wf = Workflow(name='Card workflow')
|
||||
st1 = card_wf.add_status('Status1')
|
||||
st2 = card_wf.add_status('Status2')
|
||||
|
||||
edit = st1.add_action('edit_carddata', id='_edit')
|
||||
edit.formdef_slug = carddef.url_name
|
||||
edit.target_mode = 'all'
|
||||
edit.mappings = [Mapping(field_id='0', expression='bar')]
|
||||
|
||||
jump = st1.add_action('jump', '_jump')
|
||||
jump.status = st2.id
|
||||
|
||||
card_wf.store()
|
||||
|
||||
carddef2.workflow = card_wf
|
||||
carddef2.store()
|
||||
|
||||
carddata2 = carddef2.data_class()()
|
||||
carddata2.data = {
|
||||
'1': 'card_foo',
|
||||
}
|
||||
carddata2.store()
|
||||
carddata2.just_created()
|
||||
carddata2.store()
|
||||
carddata2.perform_workflow()
|
||||
|
||||
carddata.refresh_from_storage()
|
||||
assert carddata.data['0'] == 'bar'
|
||||
|
||||
|
||||
def test_edit_carddata_manual_targeting_custom_id(pub):
|
||||
CardDef.wipe()
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'Foo Card'
|
||||
carddef.fields = [
|
||||
StringField(id='0', label='foo', varname='foo'),
|
||||
StringField(id='1', label='slug', varname='slug'),
|
||||
]
|
||||
carddef.id_template = 'card_{{form_var_slug}}'
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
|
||||
carddata = carddef.data_class()()
|
||||
carddata.data = {'0': 'foo', '1': 'foo'}
|
||||
carddata.store()
|
||||
carddata.just_created()
|
||||
carddata.store()
|
||||
assert carddata.identifier == 'card_foo'
|
||||
|
||||
carddef2 = CardDef()
|
||||
carddef2.name = 'Bar Card'
|
||||
carddef2.fields = []
|
||||
carddef2.store()
|
||||
|
||||
card_wf = Workflow(name='Card workflow')
|
||||
st1 = card_wf.add_status('Status1')
|
||||
st2 = card_wf.add_status('Status2')
|
||||
|
||||
edit = st1.add_action('edit_carddata', id='_edit')
|
||||
edit.formdef_slug = carddef.url_name
|
||||
edit.target_mode = 'manual'
|
||||
edit.target_id = 'card_foo'
|
||||
edit.mappings = [Mapping(field_id='0', expression='bar')]
|
||||
|
||||
jump = st1.add_action('jump', '_jump')
|
||||
jump.status = st2.id
|
||||
|
||||
card_wf.store()
|
||||
|
||||
carddef2.workflow = card_wf
|
||||
carddef2.store()
|
||||
|
||||
carddata2 = carddef2.data_class()()
|
||||
carddata2.data = {}
|
||||
carddata2.store()
|
||||
carddata2.just_created()
|
||||
carddata2.store()
|
||||
carddata2.perform_workflow()
|
||||
|
||||
carddata.refresh_from_storage()
|
||||
assert carddata.data['0'] == 'bar'
|
||||
|
||||
|
||||
def test_edit_carddata_from_created_object(pub):
|
||||
FormDef.wipe()
|
||||
CardDef.wipe()
|
||||
|
@ -1262,10 +1373,10 @@ def test_edit_carddata_partial_block_field(pub, admin_user):
|
|||
resp = login(get_app(pub), username='admin', password='admin').get(edit.get_admin_url())
|
||||
assert resp.form['mappings$element1$field_id'].options == [
|
||||
('', False, '---'),
|
||||
('0', False, 'foo'),
|
||||
('1', False, 'block field'),
|
||||
('1$123', True, 'block field - Test'),
|
||||
('1$234', False, 'block field - Test2'),
|
||||
('0', False, 'foo - Text (line)'),
|
||||
('1', False, 'block field - Field Block (foobar)'),
|
||||
('1$123', True, 'block field - Test - Text (line)'),
|
||||
('1$234', False, 'block field - Test2 - Text (line)'),
|
||||
]
|
||||
resp = resp.form.submit('submit')
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ from quixote import cleanup
|
|||
from wcs import sessions
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.workflow_traces import WorkflowTrace
|
||||
from wcs.workflows import Workflow
|
||||
|
||||
from ..form_pages.test_all import create_user
|
||||
|
@ -215,3 +216,36 @@ def test_choice_button_confirmation(pub):
|
|||
form = formdata.get_workflow_form(user)
|
||||
html_form = PyQuery(str(form.render()))
|
||||
assert html_form.find('button').attr('data-ask-for-confirmation') == 'Are you sure?'
|
||||
|
||||
|
||||
def test_choice_workflow_event(pub):
|
||||
user = create_user(pub)
|
||||
|
||||
workflow = Workflow(name='choice')
|
||||
st1 = workflow.add_status('Status1')
|
||||
st2 = workflow.add_status('Status2')
|
||||
choice1 = st1.add_action('choice')
|
||||
choice1.label = 'foobar1'
|
||||
choice1.by = ['logged-users']
|
||||
choice1.status = str(st2.id)
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'baz'
|
||||
formdef.fields = []
|
||||
formdef.workflow = workflow
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.user_id = user.id
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
app = login(get_app(pub), username='foo', password='foo')
|
||||
resp = app.get(formdata.get_url())
|
||||
resp = resp.form.submit(f'button{choice1.id}')
|
||||
formdata.refresh_from_storage()
|
||||
assert formdata.status == f'wf-{st2.id}'
|
||||
|
||||
traces = WorkflowTrace.select_for_formdata(formdata)
|
||||
assert [(x.event, x.event_args) for x in traces] == [('button', {'action_item_id': '1'})]
|
||||
|
|
|
@ -3,16 +3,20 @@ import datetime
|
|||
import pytest
|
||||
from quixote import cleanup, get_publisher, get_response
|
||||
|
||||
from wcs import sessions
|
||||
from wcs import fields, sessions
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.fields import EmailField, ItemField, StringField
|
||||
from wcs.formdef import FormDef
|
||||
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
|
||||
|
||||
from ..utilities import clean_temporary_pub, create_temporary_pub
|
||||
from ..backoffice_pages.test_all import create_user as create_backoffice_user
|
||||
from ..backoffice_pages.test_all import login
|
||||
from ..form_pages.test_all import create_user
|
||||
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app
|
||||
|
||||
|
||||
def setup_module(module):
|
||||
|
@ -27,6 +31,7 @@ def teardown_module(module):
|
|||
def pub(request):
|
||||
pub = create_temporary_pub()
|
||||
pub.cfg['language'] = {'language': 'en'}
|
||||
pub.cfg['identification'] = {'methods': ['password']}
|
||||
pub.write_cfg()
|
||||
req = HTTPRequest(None, {'SERVER_NAME': 'example.net', 'SCRIPT_NAME': ''})
|
||||
req.response.filter = {}
|
||||
|
@ -647,3 +652,514 @@ def test_global_timeouts_create_formdata(pub):
|
|||
pub.apply_global_action_timeouts()
|
||||
|
||||
assert subformdef.data_class().count() == 1
|
||||
|
||||
|
||||
@pytest.fixture(params=[{'attach_to_history': True}, {}])
|
||||
def create_formdata(request, pub):
|
||||
admin = create_backoffice_user(pub, is_admin=True)
|
||||
|
||||
FormDef.wipe()
|
||||
|
||||
source_formdef = FormDef()
|
||||
source_formdef.name = 'source form'
|
||||
source_formdef.workflow_roles = {'_receiver': 1}
|
||||
source_formdef.fields = [
|
||||
fields.StringField(id='0', label='string', varname='toto_string'),
|
||||
fields.FileField(id='1', label='file', varname='toto_file'),
|
||||
]
|
||||
source_formdef.store()
|
||||
|
||||
target_formdef = FormDef()
|
||||
target_formdef.name = 'target form'
|
||||
target_formdef.workflow_roles = {'_receiver': 1}
|
||||
target_formdef.backoffice_submission_roles = admin.roles[:]
|
||||
target_formdef.fields = [
|
||||
fields.StringField(id='0', label='string', varname='foo_string'),
|
||||
fields.FileField(id='1', label='file', varname='foo_file'),
|
||||
]
|
||||
target_formdef.store()
|
||||
wf = Workflow(name='create-formdata')
|
||||
|
||||
st1 = wf.add_status('New')
|
||||
st2 = wf.add_status('Resubmit')
|
||||
|
||||
jump = st1.add_action('choice', id='_resubmit')
|
||||
jump.label = 'Resubmit'
|
||||
jump.by = ['_receiver']
|
||||
jump.status = st2.id
|
||||
|
||||
create_formdata = st2.add_action('create_formdata', id='_create_formdata')
|
||||
create_formdata.varname = 'resubmitted'
|
||||
create_formdata.draft = True
|
||||
create_formdata.formdef_slug = target_formdef.url_name
|
||||
create_formdata.user_association_mode = 'keep-user'
|
||||
create_formdata.backoffice_submission = True
|
||||
create_formdata.attach_to_history = request.param.get('attach_to_history', False)
|
||||
create_formdata.mappings = [
|
||||
Mapping(field_id='0', expression='=form_var_toto_string'),
|
||||
Mapping(field_id='1', expression='=form_var_toto_file_raw'),
|
||||
]
|
||||
|
||||
redirect = st2.add_action('redirect_to_url', id='_redirect')
|
||||
redirect.url = '{{ form_links_resubmitted.form_backoffice_url }}'
|
||||
|
||||
jump = st2.add_action('jumponsubmit', id='_jump')
|
||||
jump.status = st1.id
|
||||
|
||||
wf.store()
|
||||
source_formdef.workflow_id = wf.id
|
||||
source_formdef.store()
|
||||
source_formdef.data_class().wipe()
|
||||
target_formdef.data_class().wipe()
|
||||
return locals()
|
||||
|
||||
|
||||
def test_backoffice_create_formdata_backoffice_submission(pub, create_formdata):
|
||||
# create submitting user
|
||||
user = create_formdata['pub'].user_class()
|
||||
user.name = 'Jean Darmette'
|
||||
user.email = 'jean.darmette@triffouilis.fr'
|
||||
user.store()
|
||||
|
||||
# create source formdata
|
||||
formdata = create_formdata['source_formdef'].data_class()()
|
||||
upload = PicklableUpload('/foo/bar', content_type='text/plain')
|
||||
upload.receive([b'hello world'])
|
||||
formdata.data = {
|
||||
'0': 'coucou',
|
||||
'1': upload,
|
||||
}
|
||||
formdata.user = user
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
|
||||
# agent login and go to backoffice management pages
|
||||
app = get_app(create_formdata['pub'])
|
||||
app = login(app)
|
||||
resp = app.get(create_formdata['source_formdef'].get_url(backoffice=True))
|
||||
|
||||
# click on first available formdata
|
||||
resp = resp.click('%s-%s' % (create_formdata['source_formdef'].id, formdata.id))
|
||||
target_data_class = create_formdata['target_formdef'].data_class()
|
||||
assert target_data_class.count() == 0
|
||||
# resubmit it through backoffice submission
|
||||
resp = resp.form.submit(name='button_resubmit')
|
||||
assert pub.loggederror_class.count() == 0
|
||||
assert target_data_class.count() == 1
|
||||
target_formdata = target_data_class.select()[0]
|
||||
|
||||
assert target_formdata.submission_context == {
|
||||
'orig_object_type': 'formdef',
|
||||
'orig_formdata_id': '1',
|
||||
'orig_formdef_id': '1',
|
||||
}
|
||||
assert target_formdata.submission_agent_id == str(create_formdata['admin'].id)
|
||||
assert target_formdata.user.id == user.id
|
||||
assert target_formdata.status == 'draft'
|
||||
assert target_formdata.receipt_time
|
||||
assert resp.location == 'http://example.net/backoffice/management/target-form/%s/' % target_formdata.id
|
||||
resp = resp.follow()
|
||||
assert resp.location == 'http://example.net/backoffice/submission/target-form/%s/' % target_formdata.id
|
||||
resp = resp.follow()
|
||||
# second redirect with magic-token
|
||||
resp = resp.follow()
|
||||
resp = resp.form.submit(name='submit') # -> validation
|
||||
resp = resp.form.submit(name='submit') # -> submission
|
||||
target_formdata = target_data_class.get(id=target_formdata.id)
|
||||
assert target_formdata.user.id == user.id
|
||||
assert target_formdata.status == 'wf-new'
|
||||
resp = resp.follow()
|
||||
pq = resp.pyquery.remove_namespaces()
|
||||
assert pq('.field-type-string .value').text() == 'coucou'
|
||||
assert pq('.field-type-file .value').text() == 'bar'
|
||||
|
||||
|
||||
def test_linked_forms_variables(pub, create_formdata):
|
||||
# create source formdata
|
||||
formdata = create_formdata['source_formdef'].data_class()()
|
||||
upload = PicklableUpload('/foo/bar', content_type='text/plain')
|
||||
upload.receive([b'hello world'])
|
||||
formdata.data = {
|
||||
'0': 'coucou',
|
||||
'1': upload,
|
||||
}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
formdata.store()
|
||||
formdata.jump_status('2')
|
||||
formdata.perform_workflow()
|
||||
formdata.store()
|
||||
|
||||
pub.substitutions.reset()
|
||||
pub.substitutions.feed(formdata)
|
||||
substvars = pub.substitutions.get_context_variables(mode='lazy')
|
||||
assert str(substvars['form_links_resubmitted_form_var_foo_string']) == 'coucou'
|
||||
assert 'form_links_resubmitted_form_var_foo_string' in substvars.get_flat_keys()
|
||||
|
||||
source_formdata = create_formdata['source_formdef'].data_class().select()[0]
|
||||
|
||||
app = get_app(create_formdata['pub'])
|
||||
app = login(app)
|
||||
resp = app.get(source_formdata.get_url(backoffice=True) + 'inspect')
|
||||
assert '?expand=form_links_resubmitted' in resp
|
||||
resp = app.get(source_formdata.get_url(backoffice=True) + 'inspect?expand=form_links_resubmitted')
|
||||
assert 'form_links_resubmitted_form_var_foo_string' in resp
|
||||
|
||||
# delete target formdata
|
||||
create_formdata['target_formdef'].data_class().wipe()
|
||||
resp = app.get(source_formdata.get_url(backoffice=True) + 'inspect')
|
||||
assert '?expand=form_links_resubmitted' not in resp
|
||||
assert 'form_links_resubmitted_form_var_foo_string' not in resp
|
||||
|
||||
# delete target formdef
|
||||
create_formdata['target_formdef'].remove_self()
|
||||
resp = app.get(source_formdata.get_url(backoffice=True) + 'inspect')
|
||||
|
||||
|
||||
def test_backoffice_create_formdata_map_fields_by_varname(pub, create_formdata):
|
||||
create_formdata['create_formdata'].map_fields_by_varname = True
|
||||
create_formdata['create_formdata'].mappings = []
|
||||
create_formdata['wf'].store()
|
||||
create_formdata['source_formdef'].fields = [
|
||||
fields.StringField(id='0', label='string', varname='string0'),
|
||||
fields.FileField(id='1', label='file', varname='file1'),
|
||||
fields.StringField(id='2', label='string', varname='string2', required=False),
|
||||
fields.FileField(id='3', label='file', varname='file3', required=False),
|
||||
]
|
||||
create_formdata['source_formdef'].store()
|
||||
create_formdata['target_formdef'].fields = [
|
||||
fields.StringField(id='0', label='string', varname='string0'),
|
||||
fields.FileField(id='1', label='file', varname='file1'),
|
||||
fields.StringField(id='2', label='string', varname='string2', required=False),
|
||||
fields.FileField(id='3', label='file', varname='file3', required=False),
|
||||
]
|
||||
create_formdata['target_formdef'].store()
|
||||
|
||||
# create submitting user
|
||||
user = create_formdata['pub'].user_class()
|
||||
user.name = 'Jean Darmette'
|
||||
user.email = 'jean.darmette@triffouilis.fr'
|
||||
user.store()
|
||||
|
||||
# create source formdata
|
||||
create_formdata['source_formdef'].digest_templates = {'default': 'blah'}
|
||||
create_formdata['source_formdef'].store()
|
||||
formdata = create_formdata['source_formdef'].data_class()()
|
||||
create_formdata['formdata'] = formdata
|
||||
upload = PicklableUpload('/foo/bar', content_type='text/plain')
|
||||
upload.receive([b'hello world'])
|
||||
formdata.data = {
|
||||
'0': 'coucou',
|
||||
'1': upload,
|
||||
}
|
||||
formdata.user = user
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
|
||||
# agent login and go to backoffice management pages
|
||||
app = get_app(create_formdata['pub'])
|
||||
app = login(app)
|
||||
resp = app.get(create_formdata['source_formdef'].get_url(backoffice=True))
|
||||
|
||||
# click on first available formdata
|
||||
resp = resp.click('%s-%s' % (create_formdata['source_formdef'].id, formdata.id))
|
||||
target_data_class = create_formdata['target_formdef'].data_class()
|
||||
assert target_data_class.count() == 0
|
||||
# resubmit it through backoffice submission
|
||||
resp = resp.form.submit(name='button_resubmit')
|
||||
assert pub.loggederror_class.count() == 0
|
||||
assert target_data_class.count() == 1
|
||||
target_formdata = target_data_class.select()[0]
|
||||
|
||||
assert target_formdata.submission_context == {
|
||||
'orig_object_type': 'formdef',
|
||||
'orig_formdata_id': '1',
|
||||
'orig_formdef_id': '1',
|
||||
}
|
||||
assert target_formdata.submission_agent_id == str(create_formdata['admin'].id)
|
||||
assert target_formdata.user.id == user.id
|
||||
assert target_formdata.status == 'draft'
|
||||
assert resp.location == 'http://example.net/backoffice/management/target-form/%s/' % target_formdata.id
|
||||
resp = resp.follow()
|
||||
assert resp.location == 'http://example.net/backoffice/submission/target-form/%s/' % target_formdata.id
|
||||
resp = resp.follow()
|
||||
# second redirect with magic-token
|
||||
resp = resp.follow()
|
||||
# check parent form is displayed in sidebar
|
||||
assert resp.pyquery('.extra-context--orig-data').attr.href == formdata.get_backoffice_url()
|
||||
assert resp.pyquery('.extra-context--orig-data').text() == 'source form #1-1 (blah)'
|
||||
resp = resp.form.submit(name='submit') # -> validation
|
||||
resp = resp.form.submit(name='submit') # -> submission
|
||||
target_formdata = target_data_class.get(id=target_formdata.id)
|
||||
assert target_formdata.user.id == user.id
|
||||
assert target_formdata.status == 'wf-new'
|
||||
resp = resp.follow()
|
||||
pq = resp.pyquery.remove_namespaces()
|
||||
assert pq('.field-type-string .value').text() == 'coucou'
|
||||
assert pq('.field-type-file .value').text() == 'bar'
|
||||
|
||||
resp = app.get(create_formdata['formdata'].get_url(backoffice=True))
|
||||
pq = resp.pyquery.remove_namespaces()
|
||||
assert pq('.field-type-string .value').text() == 'coucou'
|
||||
if create_formdata['create_formdata'].attach_to_history:
|
||||
assert pq('.wf-links')
|
||||
else:
|
||||
assert not pq('.wf-links')
|
||||
|
||||
|
||||
def test_backoffice_create_formdata_map_fields_by_varname_plus_empty(pub, create_formdata):
|
||||
create_formdata['create_formdata'].map_fields_by_varname = True
|
||||
create_formdata['create_formdata'].mappings = [
|
||||
Mapping(field_id='0', expression=None),
|
||||
]
|
||||
create_formdata['wf'].store()
|
||||
create_formdata['source_formdef'].fields = [
|
||||
fields.StringField(id='0', label='string', varname='string0'),
|
||||
fields.StringField(id='2', label='string', varname='string2', required=False),
|
||||
]
|
||||
create_formdata['source_formdef'].store()
|
||||
create_formdata['target_formdef'].fields = [
|
||||
fields.StringField(id='0', label='string', varname='string0'),
|
||||
fields.StringField(id='2', label='string', varname='string2', required=False),
|
||||
]
|
||||
create_formdata['target_formdef'].store()
|
||||
|
||||
# create submitting user
|
||||
user = create_formdata['pub'].user_class()
|
||||
user.name = 'Jean Darmette'
|
||||
user.email = 'jean.darmette@triffouilis.fr'
|
||||
user.store()
|
||||
|
||||
# create source formdata
|
||||
formdata = create_formdata['source_formdef'].data_class()()
|
||||
create_formdata['formdata'] = formdata
|
||||
formdata.data = {
|
||||
'0': 'foo',
|
||||
'2': 'bar',
|
||||
}
|
||||
formdata.user = user
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
|
||||
# agent login and go to backoffice management pages
|
||||
app = get_app(create_formdata['pub'])
|
||||
app = login(app)
|
||||
resp = app.get(create_formdata['source_formdef'].get_url(backoffice=True))
|
||||
|
||||
# click on first available formdata
|
||||
resp = resp.click('%s-%s' % (create_formdata['source_formdef'].id, formdata.id))
|
||||
target_data_class = create_formdata['target_formdef'].data_class()
|
||||
assert target_data_class.count() == 0
|
||||
# resubmit it through backoffice submission
|
||||
resp = resp.form.submit(name='button_resubmit')
|
||||
assert target_data_class.count() == 1
|
||||
target_formdata = target_data_class.select()[0]
|
||||
|
||||
assert target_formdata.submission_context == {
|
||||
'orig_object_type': 'formdef',
|
||||
'orig_formdata_id': '1',
|
||||
'orig_formdef_id': '1',
|
||||
}
|
||||
assert target_formdata.submission_agent_id == str(create_formdata['admin'].id)
|
||||
assert target_formdata.user.id == user.id
|
||||
assert target_formdata.status == 'draft'
|
||||
assert target_formdata.data == {'0': None, '2': 'bar'}
|
||||
|
||||
|
||||
def test_create_formdata_show_link_in_history(pub):
|
||||
FormDef.wipe()
|
||||
pub.tracking_code_class.wipe()
|
||||
|
||||
target_formdef = FormDef()
|
||||
target_formdef.name = 'target-form'
|
||||
target_formdef.fields = [
|
||||
fields.StringField(id='0', label='string', varname='foo_string'),
|
||||
]
|
||||
target_formdef.store()
|
||||
|
||||
wf = Workflow(name='create-formdata')
|
||||
wf.possible_status = Workflow.get_default_workflow().possible_status[:]
|
||||
create = wf.possible_status[1].add_action('create_formdata', id='_create', prepend=True)
|
||||
create.label = 'create a new linked form'
|
||||
create.varname = 'resubmitted'
|
||||
create.mappings = [
|
||||
Mapping(field_id='0', expression='="coincoin"'),
|
||||
]
|
||||
wf.store()
|
||||
|
||||
source_formdef = FormDef()
|
||||
source_formdef.name = 'source-form'
|
||||
source_formdef.fields = []
|
||||
source_formdef.workflow_id = wf.id
|
||||
source_formdef.enable_tracking_codes = True
|
||||
source_formdef.store()
|
||||
|
||||
create.formdef_slug = target_formdef.url_name
|
||||
create.attach_to_history = True
|
||||
wf.store()
|
||||
|
||||
source_formdef.data_class().wipe()
|
||||
target_formdef.data_class().wipe()
|
||||
|
||||
create_user(pub)
|
||||
app = login(get_app(pub), username='foo', password='foo')
|
||||
resp = app.get('/source-form/')
|
||||
resp = resp.forms[0].submit('submit')
|
||||
assert 'Check values then click submit.' in resp.text
|
||||
resp = resp.forms[0].submit('submit')
|
||||
assert resp.status_int == 302
|
||||
resp = resp.follow()
|
||||
assert 'The form has been recorded' in resp.text
|
||||
|
||||
formdata = source_formdef.data_class().select()[0]
|
||||
|
||||
# logged access: show link to created formdata
|
||||
resp = app.get('/source-form/%s/' % formdata.id)
|
||||
assert 'The form has been recorded on' in resp.text
|
||||
assert 'New form "target-form" created' in resp.text
|
||||
assert resp.pyquery('.wf-links a')
|
||||
|
||||
# anonymous access via tracking code: no link
|
||||
app = get_app(pub)
|
||||
resp = app.get('/code/%s/load' % formdata.tracking_code)
|
||||
resp = resp.follow()
|
||||
assert 'The form has been recorded on' in resp.text
|
||||
assert 'New form "target-form" created' not in resp.text
|
||||
assert not resp.pyquery('.wf-links a')
|
||||
|
||||
|
||||
def test_create_formdata_multiple(pub):
|
||||
FormDef.wipe()
|
||||
pub.tracking_code_class.wipe()
|
||||
|
||||
target_formdef = FormDef()
|
||||
target_formdef.name = 'target-form'
|
||||
target_formdef.fields = [
|
||||
fields.StringField(id='0', label='string', varname='foo_string'),
|
||||
]
|
||||
target_formdef.store()
|
||||
|
||||
wf = Workflow(name='create-formdata')
|
||||
wf.possible_status = Workflow.get_default_workflow().possible_status[:]
|
||||
global_action = wf.add_global_action('create formdata')
|
||||
trigger = global_action.triggers[0]
|
||||
trigger.roles = ['_submitter']
|
||||
create = global_action.add_action('create_formdata')
|
||||
create.label = 'create a new linked form'
|
||||
create.varname = 'resubmitted'
|
||||
create.mappings = [Mapping(field_id='0', expression='plop')]
|
||||
wf.store()
|
||||
|
||||
source_formdef = FormDef()
|
||||
source_formdef.name = 'source-form'
|
||||
source_formdef.fields = []
|
||||
source_formdef.workflow_id = wf.id
|
||||
source_formdef.enable_tracking_codes = True
|
||||
source_formdef.store()
|
||||
|
||||
create.formdef_slug = target_formdef.url_name
|
||||
wf.store()
|
||||
|
||||
source_formdef.data_class().wipe()
|
||||
target_formdef.data_class().wipe()
|
||||
|
||||
user = create_user(pub)
|
||||
|
||||
formdata = source_formdef.data_class()()
|
||||
formdata.user_id = user.id
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
formdata2 = source_formdef.data_class()()
|
||||
formdata2.user_id = user.id
|
||||
formdata2.just_created()
|
||||
formdata2.store()
|
||||
|
||||
app = login(get_app(pub), username='foo', password='foo')
|
||||
resp = app.get(formdata.get_url())
|
||||
|
||||
resp = resp.form.submit('button-action-1')
|
||||
assert target_formdef.data_class().count() == 1
|
||||
|
||||
resp = app.get(formdata.get_url())
|
||||
resp = resp.form.submit('button-action-1')
|
||||
assert target_formdef.data_class().count() == 2
|
||||
|
||||
# do it from another formdata (should not trigger recursive call detection)
|
||||
resp = app.get(formdata2.get_url())
|
||||
resp = resp.form.submit('button-action-1')
|
||||
assert target_formdef.data_class().count() == 3
|
||||
|
||||
|
||||
@pytest.mark.parametrize('mode', ['single', 'partial'])
|
||||
def test_create_formdata_edit_single_or_partial_pages(pub, mode):
|
||||
FormDef.wipe()
|
||||
pub.tracking_code_class.wipe()
|
||||
|
||||
target_formdef = FormDef()
|
||||
target_formdef.name = 'target-form'
|
||||
target_formdef.fields = [
|
||||
fields.PageField(id='1', label='page1'),
|
||||
fields.StringField(id='2', label='string', varname='foo_string'),
|
||||
fields.PageField(id='3', label='page2', varname='page2'),
|
||||
fields.StringField(id='4', label='string2', varname='bar_string'),
|
||||
fields.PageField(id='4', label='page3'),
|
||||
]
|
||||
target_formdef.store()
|
||||
|
||||
wf = Workflow(name='create-formdata')
|
||||
wf.possible_status = Workflow.get_default_workflow().possible_status[:]
|
||||
create = wf.possible_status[1].add_action('create_formdata', id='_create', prepend=True)
|
||||
create.label = 'create a new linked form'
|
||||
create.varname = 'resubmitted'
|
||||
create.draft = True
|
||||
create.formdef_slug = target_formdef.url_name
|
||||
create.attach_to_history = True
|
||||
create.draft_edit_operation_mode = mode
|
||||
create.page_identifier = 'page2'
|
||||
create.mappings = [
|
||||
Mapping(field_id='2', expression='blah1'),
|
||||
Mapping(field_id='4', expression='blah2'),
|
||||
]
|
||||
wf.store()
|
||||
|
||||
source_formdef = FormDef()
|
||||
source_formdef.name = 'source-form'
|
||||
source_formdef.fields = []
|
||||
source_formdef.workflow_id = wf.id
|
||||
source_formdef.enable_tracking_codes = True
|
||||
source_formdef.store()
|
||||
|
||||
source_formdef.data_class().wipe()
|
||||
target_formdef.data_class().wipe()
|
||||
|
||||
create_user(pub)
|
||||
app = login(get_app(pub), username='foo', password='foo')
|
||||
resp = app.get('/source-form/')
|
||||
resp = resp.forms[0].submit('submit') # -> validation
|
||||
resp = resp.forms[0].submit('submit').follow() # -> submit
|
||||
assert 'The form has been recorded' in resp.text
|
||||
|
||||
created_url = resp.pyquery('.wf-links a')[0].attrib['href']
|
||||
resp = app.get(created_url).follow()
|
||||
|
||||
if mode == 'single':
|
||||
assert resp.pyquery('.wcs-step').length == 2
|
||||
else:
|
||||
assert resp.pyquery('.wcs-step').length == 3
|
||||
assert resp.pyquery('.wcs-step.current .label').text() == 'page2 (current step)'
|
||||
assert resp.forms[1]['f4'].value == 'blah2'
|
||||
|
||||
if mode == 'partial':
|
||||
resp = resp.forms[1].submit('submit') # -> page 3
|
||||
assert resp.pyquery('.wcs-step.current .label').text() == 'page3 (current step)'
|
||||
|
||||
resp = resp.forms[1].submit('submit') # -> validation
|
||||
resp = resp.forms[1].submit('submit') # -> submit
|
||||
assert target_formdef.data_class().count() == 1
|
||||
formdata = target_formdef.data_class().select()[0]
|
||||
assert formdata.data == {'2': 'blah1', '4': 'blah2'}
|
|
@ -1,9 +1,12 @@
|
|||
import os
|
||||
|
||||
import pytest
|
||||
from quixote import cleanup
|
||||
|
||||
from wcs.workflows import Workflow
|
||||
|
||||
from ..utilities import clean_temporary_pub, create_temporary_pub
|
||||
from ..admin_pages.test_all import create_superuser
|
||||
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
|
||||
|
||||
|
||||
def setup_module(module):
|
||||
|
@ -42,3 +45,67 @@ def test_display_message_migrate(pub):
|
|||
workflow.migrate()
|
||||
assert not workflow.possible_status[0].items[0].level
|
||||
assert workflow.possible_status[0].items[0].message == '<div class="errornotice blah">message</div>'
|
||||
|
||||
|
||||
def test_display_message_rich_text(pub):
|
||||
create_superuser(pub)
|
||||
|
||||
workflow = Workflow(name='display message to')
|
||||
st1 = workflow.add_status('Status1', 'st1')
|
||||
display_message = st1.add_action('displaymsg')
|
||||
display_message.message = '<p>hello world</p>'
|
||||
workflow.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get(display_message.get_admin_url())
|
||||
assert resp.pyquery('godo-editor') # godo
|
||||
|
||||
display_message.message = '<table><tr><td>hello world</td></tr></table>'
|
||||
workflow.store()
|
||||
resp = app.get(display_message.get_admin_url())
|
||||
assert resp.pyquery('textarea[data-config]') # ckeditor
|
||||
|
||||
pub.site_options.set('options', 'rich-text-wf-displaymsg', 'auto-ckeditor-textarea')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
display_message.message = '<table><tr><td>hello world</td></tr></table>'
|
||||
workflow.store()
|
||||
resp = app.get(display_message.get_admin_url())
|
||||
assert resp.pyquery('textarea[data-config]') # ckeditor
|
||||
|
||||
display_message.message = '<ul>{% for item in lists %}<li>{{ item }}</li>{% endfor %}</ul>'
|
||||
workflow.store()
|
||||
resp = app.get(display_message.get_admin_url())
|
||||
assert resp.pyquery('textarea:not([data-config])') # plain textarea
|
||||
|
||||
pub.site_options.set('options', 'rich-text-wf-displaymsg', 'auto-textarea')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
resp = app.get(display_message.get_admin_url())
|
||||
assert resp.pyquery('textarea:not([data-config])') # plain textarea
|
||||
|
||||
pub.site_options.set('options', 'rich-text-wf-displaymsg', 'auto-ckeditor')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
resp = app.get(display_message.get_admin_url())
|
||||
assert resp.pyquery('textarea[data-config]') # ckeditor
|
||||
|
||||
display_message.message = '<p>simple</p>'
|
||||
workflow.store()
|
||||
pub.site_options.set('options', 'rich-text-wf-displaymsg', 'ckeditor')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
resp = app.get(display_message.get_admin_url())
|
||||
assert resp.pyquery('textarea[data-config]') # ckeditor
|
||||
|
||||
pub.site_options.set('options', 'rich-text-wf-displaymsg', 'godo')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
resp = app.get(display_message.get_admin_url())
|
||||
assert resp.pyquery('godo-editor') # godo
|
||||
|
||||
pub.site_options.set('options', 'rich-text-wf-displaymsg', 'textarea')
|
||||
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
|
||||
pub.site_options.write(fd)
|
||||
resp = app.get(display_message.get_admin_url())
|
||||
assert resp.pyquery('textarea:not([data-config])') # plain textarea
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
import pytest
|
||||
from quixote import cleanup
|
||||
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.workflows import Workflow
|
||||
|
||||
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app
|
||||
|
||||
|
||||
def setup_module(module):
|
||||
cleanup()
|
||||
|
||||
|
||||
def teardown_module(module):
|
||||
clean_temporary_pub()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pub():
|
||||
pub = create_temporary_pub()
|
||||
pub.cfg['language'] = {'language': 'en'}
|
||||
pub.write_cfg()
|
||||
return pub
|
||||
|
||||
|
||||
def test_editable_line_details(pub):
|
||||
workflow = Workflow(name='test')
|
||||
st1 = workflow.add_status('Status1', 'st1')
|
||||
action = st1.add_action('editable')
|
||||
|
||||
assert action.get_line_details() == 'not completed'
|
||||
|
||||
role = pub.role_class(name='foorole')
|
||||
role.store()
|
||||
action.by = [role.id]
|
||||
assert action.get_line_details() == '"Edit Form", by foorole'
|
||||
|
||||
action.label = 'foobar'
|
||||
assert action.get_line_details() == '"foobar", by foorole'
|
||||
|
||||
|
||||
def test_editable_set_marker(pub):
|
||||
FormDef.wipe()
|
||||
Workflow.wipe()
|
||||
|
||||
workflow = Workflow(name='test')
|
||||
st1 = workflow.add_status('Status1', 'st1')
|
||||
st2 = workflow.add_status('Status2', 'st2')
|
||||
editable = st1.add_action('editable')
|
||||
editable.status = st2.id
|
||||
editable.set_marker_on_status = True
|
||||
editable.by = ['_submitter']
|
||||
back = st2.add_action('choice')
|
||||
back.label = 'go back'
|
||||
back.status = '_previous'
|
||||
back.by = ['_submitter']
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'baz'
|
||||
formdef.workflow = workflow
|
||||
formdef.store()
|
||||
|
||||
resp = get_app(pub).get(formdef.get_url())
|
||||
resp = resp.form.submit('submit') # -> validation page
|
||||
resp = resp.form.submit('submit').follow() # -> done
|
||||
formdata = formdef.data_class().select()[0]
|
||||
|
||||
# edit
|
||||
resp = resp.form.submit(f'button{editable.id}').follow()
|
||||
resp = resp.form.submit('submit').follow() # -> done
|
||||
|
||||
formdata.refresh_from_storage()
|
||||
assert formdata.get_status().id == st2.id
|
||||
assert formdata.workflow_data.get('_markers_stack')
|
||||
|
||||
# back
|
||||
resp = resp.form.submit(f'button{editable.id}').follow()
|
||||
formdata.refresh_from_storage()
|
||||
assert formdata.get_status().id == st1.id
|
||||
assert not formdata.workflow_data.get('_markers_stack')
|
|
@ -1,6 +1,7 @@
|
|||
import base64
|
||||
import json
|
||||
import os
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from django.utils.encoding import force_bytes, force_str
|
||||
|
@ -12,11 +13,13 @@ from wcs.formdef import FormDef
|
|||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.qommon.substitution import CompatibilityNamesDict
|
||||
from wcs.qommon.upload_storage import PicklableUpload
|
||||
from wcs.testdef import TestDef
|
||||
from wcs.wf.backoffice_fields import SetBackofficeFieldsWorkflowStatusItem
|
||||
from wcs.wf.sendmail import SendmailWorkflowStatusItem
|
||||
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef
|
||||
|
||||
from ..utilities import MockSubstitutionVariables, clean_temporary_pub, create_temporary_pub
|
||||
from ..admin_pages.test_all import create_superuser
|
||||
from ..utilities import MockSubstitutionVariables, clean_temporary_pub, create_temporary_pub, get_app, login
|
||||
|
||||
|
||||
def setup_module(module):
|
||||
|
@ -31,6 +34,7 @@ def teardown_module(module):
|
|||
def pub(request):
|
||||
pub = create_temporary_pub()
|
||||
pub.cfg['language'] = {'language': 'en'}
|
||||
pub.cfg['identification'] = {'methods': ['password']}
|
||||
pub.write_cfg()
|
||||
req = HTTPRequest(None, {'SERVER_NAME': 'example.net', 'SCRIPT_NAME': ''})
|
||||
req.response.filter = {}
|
||||
|
@ -38,6 +42,7 @@ def pub(request):
|
|||
pub._set_request(req)
|
||||
req.session = sessions.BasicSession(id=1)
|
||||
pub.set_config(req)
|
||||
TestDef.wipe()
|
||||
return pub
|
||||
|
||||
|
||||
|
@ -715,3 +720,56 @@ def test_email_computed_recipients(pub, emails):
|
|||
assert emails.count() == 1
|
||||
assert set(formdata.evolution[-1].parts[-1].addresses) == {'user1@example.com'}
|
||||
formdata.evolution[-1].parts = []
|
||||
|
||||
|
||||
@pytest.mark.parametrize('req', [True, False])
|
||||
def test_email_invalid_recipients(pub, req):
|
||||
if req is False:
|
||||
pub._set_request(None)
|
||||
|
||||
FormDef.wipe()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'baz'
|
||||
formdef.fields = []
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
item = SendmailWorkflowStatusItem()
|
||||
item.varname = 'test'
|
||||
item.to = ['invalid,']
|
||||
item.subject = 'xxx'
|
||||
item.body = 'XXX'
|
||||
|
||||
with mock.patch('wcs.qommon.emails.EmailToSend.__call__') as send_email_job:
|
||||
item.perform(formdata)
|
||||
if req:
|
||||
get_response().process_after_jobs()
|
||||
assert send_email_job.call_count == 0
|
||||
|
||||
|
||||
def test_workflows_edit_sendmail_action(pub):
|
||||
create_superuser(pub)
|
||||
Workflow.wipe()
|
||||
workflow = Workflow(name='foo')
|
||||
st1 = workflow.add_status(name='baz')
|
||||
workflow.store()
|
||||
|
||||
app = login(get_app(pub))
|
||||
resp = app.get(st1.get_admin_url())
|
||||
|
||||
resp.forms[0]['action-interaction'] = 'Email'
|
||||
resp = resp.forms[0].submit()
|
||||
resp = resp.follow()
|
||||
|
||||
resp = resp.click('Email')
|
||||
resp.form['to$element0$choice'] = '__other'
|
||||
resp.form['to$element0$other$value_template'] = '{{ test }}'
|
||||
resp.form.submit('submit')
|
||||
|
||||
workflow.refresh_from_storage()
|
||||
assert workflow.possible_status[0].items[0].to == ['{{ test }}']
|
||||
|
|
|
@ -14,6 +14,7 @@ from webtest import Radio, Upload
|
|||
|
||||
from wcs import sessions
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.fields import (
|
||||
BlockField,
|
||||
BoolField,
|
||||
|
@ -35,7 +36,7 @@ from wcs.qommon import force_str
|
|||
from wcs.qommon.form import UploadedFile
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.qommon.upload_storage import PicklableUpload
|
||||
from wcs.wf.export_to_model import ExportToModel, UploadValidationError, transform_to_pdf
|
||||
from wcs.wf.export_to_model import ExportToModel, transform_to_pdf
|
||||
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef
|
||||
|
||||
from ..admin_pages.test_all import create_superuser
|
||||
|
@ -319,6 +320,8 @@ def test_export_to_model_xml(pub):
|
|||
item.attach_to_history = True
|
||||
|
||||
def run(template, filename='/foo/template.xml', content_type='application/xml'):
|
||||
formdata.evolution[-1].parts = None
|
||||
formdata.store()
|
||||
pub.loggederror_class.wipe()
|
||||
upload = QuixoteUpload(filename, content_type=content_type)
|
||||
upload.fp = io.BytesIO()
|
||||
|
@ -329,8 +332,9 @@ def test_export_to_model_xml(pub):
|
|||
pub.substitutions.reset()
|
||||
pub.substitutions.feed(formdata)
|
||||
item.perform(formdata)
|
||||
with open(formdata.evolution[0].parts[-1].get_file_path()) as fd:
|
||||
return fd.read()
|
||||
if formdata.evolution[0].parts:
|
||||
with open(formdata.evolution[0].parts[-1].get_file_path()) as fd:
|
||||
return fd.read()
|
||||
|
||||
# good XML
|
||||
assert run(template='<a>{{ form_var_string }}</a>') == '<a>écho</a>'
|
||||
|
@ -340,23 +344,53 @@ def test_export_to_model_xml(pub):
|
|||
assert run(template='<a>{{ form_var_string }}</a>', filename='/foo/template.svg') == '<a>écho</a>'
|
||||
|
||||
# unknown file format
|
||||
with pytest.raises(UploadValidationError) as e:
|
||||
run(
|
||||
template='<a>{{ form_var_string }}</a>',
|
||||
filename='/foo/template.txt',
|
||||
content_type='application/octet-stream',
|
||||
)
|
||||
assert str(e.value) == 'Only OpenDocument and XML files can be used.'
|
||||
assert not run(
|
||||
template='<a>{{ form_var_string }}</a>',
|
||||
filename='/foo/template.txt',
|
||||
content_type='application/octet-stream',
|
||||
)
|
||||
assert pub.loggederror_class.count() == 1
|
||||
assert pub.loggederror_class.select()[0].summary == 'Only OpenDocument and XML files can be used.'
|
||||
|
||||
# invalid UTF-8
|
||||
with pytest.raises(UploadValidationError) as e:
|
||||
assert run(template=b'<name>test \xE0 {{form_var_string}}</name>') == ''
|
||||
assert str(e.value) == 'XML model files must be UTF-8.'
|
||||
assert not run(template=b'<name>test \xE0 {{form_var_string}}</name>')
|
||||
assert pub.loggederror_class.count() == 1
|
||||
assert pub.loggederror_class.select()[0].summary == 'XML model files must be UTF-8.'
|
||||
|
||||
# malformed XML
|
||||
assert run(template='<a>{{ form_var_string }}<a>') == '<a>écho<a>'
|
||||
# on error in the XML correctness no exception is raised but an error is logged
|
||||
assert pub.loggederror_class.count() == 1
|
||||
assert pub.loggederror_class.select()[0].summary == 'The rendered template is not a valid XML document.'
|
||||
|
||||
|
||||
def test_export_to_model_disabled_rtf(pub):
|
||||
formdef = FormDef()
|
||||
formdef.name = 'foo-export-to-template-with-django'
|
||||
formdef.fields = [
|
||||
StringField(id='1', label='String', varname='string'),
|
||||
]
|
||||
formdef.store()
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
item = ExportToModel()
|
||||
item.method = 'non-interactive'
|
||||
item.attach_to_history = True
|
||||
upload = QuixoteUpload('test.rtf', content_type='application/rtf')
|
||||
upload.fp = io.BytesIO()
|
||||
upload.fp.write(b'{\\rtf...')
|
||||
upload.fp.seek(0)
|
||||
item.model_file = UploadedFile(pub.app_dir, None, upload)
|
||||
item.convert_to_pdf = False
|
||||
pub.substitutions.reset()
|
||||
pub.substitutions.feed(formdata)
|
||||
|
||||
pub.loggederror_class.wipe()
|
||||
item.perform(formdata)
|
||||
assert pub.loggederror_class.count() == 1
|
||||
assert pub.loggederror_class.select()[0].summary == 'Only OpenDocument and XML files can be used.'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('filename', ['template-form-details.odt', 'template-form-details-no-styles.odt'])
|
||||
|
@ -512,6 +546,46 @@ def test_interactive_create_doc_and_jump_on_submit(pub):
|
|||
assert formdef.data_class().select()[0].status == f'wf-{st1.id}' # no change
|
||||
|
||||
|
||||
def test_interactive_create_doc_update_ts(pub):
|
||||
wf = Workflow(name='create doc')
|
||||
st0 = wf.add_status('Status0')
|
||||
export_to_model = st0.add_action('export_to_model', id='_export_to_model')
|
||||
export_to_model.by = ['_submitter', '_receiver']
|
||||
export_to_model.method = 'interactive'
|
||||
export_to_model.convert_to_pdf = False
|
||||
template_filename = os.path.join(os.path.dirname(__file__), '..', 'template.odt')
|
||||
with open(template_filename, 'rb') as fd:
|
||||
template = fd.read()
|
||||
upload = QuixoteUpload('/foo/template.odt', content_type='application/octet-stream')
|
||||
upload.fp = io.BytesIO()
|
||||
upload.fp.write(template)
|
||||
upload.fp.seek(0)
|
||||
export_to_model.model_file = UploadedFile(pub.app_dir, None, upload)
|
||||
wf.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = [StringField(id='1', label='string', varname='toto')]
|
||||
formdef.workflow_id = wf.id
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
app = get_app(pub)
|
||||
resp = app.get(formdef.get_url())
|
||||
resp.form['f1'] = 'test'
|
||||
resp = resp.form.submit('submit')
|
||||
resp = resp.form.submit('submit').follow()
|
||||
|
||||
resp2 = resp.form.submit(f'button{export_to_model.id}').follow().follow()
|
||||
assert resp2.body.startswith(b'PK') # odt
|
||||
|
||||
# emulate js that will update workflow form ts field
|
||||
resp_js = app.get(resp.request.path + 'tsupdate')
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert resp_js.json['ts'] != resp.forms['wf-actions']['_ts']
|
||||
assert str(formdata.last_update_time.timestamp()) == resp_js.json['ts']
|
||||
|
||||
|
||||
def test_workflows_edit_export_to_model_action(pub):
|
||||
create_superuser(pub)
|
||||
Workflow.wipe()
|
||||
|
@ -674,3 +748,120 @@ def test_workflows_edit_export_to_model_action_check_template(pub):
|
|||
.text()
|
||||
.startswith('syntax error in Django template: Invalid block')
|
||||
)
|
||||
|
||||
# error in field declaration
|
||||
zip_out_fp = io.BytesIO()
|
||||
with open(os.path.join(os.path.dirname(__file__), '../template.odt'), 'rb') as fd:
|
||||
with zipfile.ZipFile(fd, mode='r') as zip_in, zipfile.ZipFile(zip_out_fp, mode='w') as zip_out:
|
||||
for filename in zip_in.namelist():
|
||||
content = zip_in.read(filename)
|
||||
if filename == 'content.xml':
|
||||
assert b'"[if-any form_name][form_name][end]"' in content
|
||||
content = content.replace(
|
||||
b'"[if-any form_name][form_name][end]"', b'"{% if foo %}{{ foo }}{% end %}"'
|
||||
)
|
||||
zip_out.writestr(filename, content)
|
||||
model_content = zip_out_fp.getvalue()
|
||||
resp.form['model_file'] = Upload('test.odt', model_content)
|
||||
resp = resp.form.submit('submit')
|
||||
assert (
|
||||
resp.pyquery('#form_error_model_file')
|
||||
.text()
|
||||
.startswith('syntax error in Django template: Invalid block')
|
||||
)
|
||||
|
||||
# error in unused field declaration
|
||||
zip_out_fp = io.BytesIO()
|
||||
with open(os.path.join(os.path.dirname(__file__), '../template.odt'), 'rb') as fd:
|
||||
with zipfile.ZipFile(fd, mode='r') as zip_in, zipfile.ZipFile(zip_out_fp, mode='w') as zip_out:
|
||||
for filename in zip_in.namelist():
|
||||
content = zip_in.read(filename)
|
||||
if filename == 'content.xml':
|
||||
assert b'office:string-value="[if-any form_name][form_name][end]"' in content
|
||||
content = content.replace(
|
||||
b'"[if-any form_name][form_name][end]"', b'"{% if foo %}{{ foo }}{% end %}"'
|
||||
)
|
||||
content = content.replace(
|
||||
b'text:user-field-get text:name="nawak"', b'text:user-field-get text:name="other"'
|
||||
)
|
||||
zip_out.writestr(filename, content)
|
||||
model_content = zip_out_fp.getvalue()
|
||||
resp.form['model_file'] = Upload('test.odt', model_content)
|
||||
resp.form.submit('submit').follow() # success
|
||||
|
||||
|
||||
def test_export_to_model_from_template(pub):
|
||||
CardDef.wipe()
|
||||
carddef = CardDef()
|
||||
carddef.name = 'card'
|
||||
carddef.fields = [
|
||||
FileField(id='1', label='File', varname='file'),
|
||||
StringField(id='2', label='String', varname='string'),
|
||||
]
|
||||
carddef.store()
|
||||
|
||||
template_filename = os.path.join(os.path.dirname(__file__), '..', 'template.odt')
|
||||
with open(template_filename, 'rb') as fd:
|
||||
template = fd.read()
|
||||
upload = QuixoteUpload('/foo/template.odt', content_type='application/octet-stream')
|
||||
upload.fp = io.BytesIO()
|
||||
upload.fp.write(template)
|
||||
upload.fp.seek(0)
|
||||
|
||||
carddata = carddef.data_class()()
|
||||
carddata.data = {'1': upload, '2': 'blah'}
|
||||
carddata.just_created()
|
||||
carddata.store()
|
||||
|
||||
wf = Workflow(name='test_export_to_model_from_template')
|
||||
wf.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(wf)
|
||||
wf.backoffice_fields_formdef.fields = [
|
||||
FileField(id='bo1', label='bo field 1', varname='backoffice_file1'),
|
||||
]
|
||||
st1 = wf.add_status('Status1')
|
||||
wf.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'foo-export'
|
||||
formdef.fields = [
|
||||
StringField(id='1', label='String', varname='string'),
|
||||
]
|
||||
formdef.workflow_id = wf.id
|
||||
formdef.store()
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
pub.substitutions.feed(formdata)
|
||||
|
||||
item = ExportToModel()
|
||||
item.method = 'non-interactive'
|
||||
item.convert_to_pdf = False
|
||||
item.model_file_mode = 'template'
|
||||
item.model_file_template = '{{cards|objects:"card"|first|get:"form_var_file" }}'
|
||||
item.parent = st1
|
||||
item.backoffice_filefield_id = 'bo1'
|
||||
item.perform(formdata)
|
||||
|
||||
assert 'bo1' in formdata.data
|
||||
fbo1 = formdata.data['bo1']
|
||||
assert fbo1.base_filename == 'template.odt'
|
||||
assert fbo1.content_type == 'application/octet-stream'
|
||||
with zipfile.ZipFile(fbo1.get_file()) as zfile:
|
||||
assert b'foo-export' in zfile.read('content.xml')
|
||||
|
||||
pub.loggederror_class.wipe()
|
||||
item.model_file_template = '{{cards|objects:"card"|first|get:"form_var_string" }}'
|
||||
formdata.data = {}
|
||||
item.perform(formdata)
|
||||
assert 'bo1' not in formdata.data
|
||||
assert pub.loggederror_class.count() == 1
|
||||
assert pub.loggederror_class.select()[0].summary == 'Invalid value obtained for model file (\'blah\')'
|
||||
|
||||
pub.loggederror_class.wipe()
|
||||
item.model_file_template = '{% if foo %}{{ foo }}{% end %}' # invalid template
|
||||
formdata.data = {}
|
||||
item.perform(formdata)
|
||||
assert 'bo1' not in formdata.data
|
||||
assert pub.loggederror_class.count() == 1
|
||||
assert pub.loggederror_class.select()[0].summary == 'Failed to evaluate template for action'
|
||||
|
|
|
@ -767,11 +767,16 @@ def test_call_external_remove_self(pub):
|
|||
CardDef.wipe()
|
||||
FormDef.wipe()
|
||||
|
||||
wf_card = Workflow(name='call-back')
|
||||
wf_card.add_status('st1')
|
||||
wf_card.store()
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'My card'
|
||||
carddef.fields = [
|
||||
StringField(id='1', label='string'),
|
||||
]
|
||||
carddef.workflow_id = wf_card.id
|
||||
carddef.store()
|
||||
|
||||
carddata = carddef.data_class()()
|
||||
|
@ -798,9 +803,6 @@ def test_call_external_remove_self(pub):
|
|||
action.target_id = '%s' % carddata.id
|
||||
wf_form.store()
|
||||
|
||||
wf_card = Workflow(name='call-back')
|
||||
wf_card.add_status('st1')
|
||||
wf_card.store()
|
||||
card_action = wf_card.add_global_action('Call back', 'call')
|
||||
card_trigger = card_action.append_trigger('webservice')
|
||||
card_trigger.identifier = 'call'
|
||||
|
@ -814,12 +816,9 @@ def test_call_external_remove_self(pub):
|
|||
formdef.workflow_id = wf_form.id
|
||||
formdef.store()
|
||||
|
||||
carddef.workflow_id = wf_card.id
|
||||
carddef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.just_created()
|
||||
url = formdata.perform_workflow()
|
||||
with pytest.raises(KeyError):
|
||||
formdata.refresh_from_storage() # was deleted
|
||||
|
|
|
@ -461,6 +461,7 @@ def test_frontoffice_workflow_form_with_impossible_condition(pub):
|
|||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.user_id = user.id
|
||||
formdata.just_created()
|
||||
formdata.status = 'wf-new'
|
||||
formdata.store()
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import datetime
|
||||
import time
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from pyquery import PyQuery
|
||||
from quixote import cleanup
|
||||
|
||||
from wcs.fields import DateField, StringField
|
||||
|
@ -11,7 +11,8 @@ from wcs.qommon.http_request import HTTPRequest
|
|||
from wcs.wf.jump import JumpWorkflowStatusItem, _apply_timeouts
|
||||
from wcs.workflows import Workflow, perform_items
|
||||
|
||||
from ..utilities import clean_temporary_pub, create_temporary_pub
|
||||
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
|
||||
from .test_all import admin_user # noqa pylint: disable=unused-import
|
||||
|
||||
|
||||
def setup_module(module):
|
||||
|
@ -26,6 +27,7 @@ def teardown_module(module):
|
|||
def pub(request):
|
||||
pub = create_temporary_pub()
|
||||
pub.cfg['language'] = {'language': 'en'}
|
||||
pub.cfg['identification'] = {'methods': ['password']}
|
||||
pub.write_cfg()
|
||||
req = HTTPRequest(None, {'SERVER_NAME': 'example.net', 'SCRIPT_NAME': ''})
|
||||
req.response.filter = {}
|
||||
|
@ -37,11 +39,8 @@ def pub(request):
|
|||
|
||||
def rewind(formdata, seconds):
|
||||
# utility function to move formdata back in time
|
||||
def rewind_time(timetuple):
|
||||
return time.localtime(datetime.datetime.fromtimestamp(time.mktime(timetuple) - seconds).timestamp())
|
||||
|
||||
formdata.receipt_time = rewind_time(formdata.receipt_time)
|
||||
formdata.evolution[-1].time = rewind_time(formdata.evolution[-1].time)
|
||||
formdata.receipt_time = formdata.receipt_time - datetime.timedelta(seconds=seconds)
|
||||
formdata.evolution[-1].time = formdata.evolution[-1].time - datetime.timedelta(seconds=seconds)
|
||||
|
||||
|
||||
def test_jump_nothing(pub):
|
||||
|
@ -159,8 +158,16 @@ def test_jump_bad_python_condition(pub):
|
|||
assert logged_error.summary == 'Failed to evaluate condition'
|
||||
assert logged_error.exception_class == 'NameError'
|
||||
assert logged_error.exception_message == "name 'form_var_foobar' is not defined"
|
||||
assert logged_error.expression == 'form_var_foobar == 0'
|
||||
assert logged_error.expression_type == 'python'
|
||||
assert logged_error.context == {
|
||||
'stack': [
|
||||
{
|
||||
'condition': 'form_var_foobar == 0',
|
||||
'condition_type': 'python',
|
||||
'source_label': 'Automatic Jump',
|
||||
'source_url': '',
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
pub.loggederror_class.wipe()
|
||||
item.condition = {'type': 'python', 'value': '~ invalid ~'}
|
||||
|
@ -169,9 +176,17 @@ def test_jump_bad_python_condition(pub):
|
|||
logged_error = pub.loggederror_class.select()[0]
|
||||
assert logged_error.summary == 'Failed to evaluate condition'
|
||||
assert logged_error.exception_class == 'SyntaxError'
|
||||
assert logged_error.exception_message == 'unexpected EOF while parsing (<string>, line 1)'
|
||||
assert logged_error.expression == '~ invalid ~'
|
||||
assert logged_error.expression_type == 'python'
|
||||
assert logged_error.exception_message == 'invalid syntax (<string>, line 1)'
|
||||
assert logged_error.context == {
|
||||
'stack': [
|
||||
{
|
||||
'condition': '~ invalid ~',
|
||||
'source_url': '',
|
||||
'source_label': 'Automatic Jump',
|
||||
'condition_type': 'python',
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def test_jump_django_conditions(pub):
|
||||
|
@ -208,8 +223,16 @@ def test_jump_django_conditions(pub):
|
|||
assert logged_error.summary == 'Failed to evaluate condition'
|
||||
assert logged_error.exception_class == 'TemplateSyntaxError'
|
||||
assert logged_error.exception_message == "Could not parse the remainder: '~' from '~'"
|
||||
assert logged_error.expression == '~ invalid ~'
|
||||
assert logged_error.expression_type == 'django'
|
||||
assert logged_error.context == {
|
||||
'stack': [
|
||||
{
|
||||
'condition': '~ invalid ~',
|
||||
'source_url': '',
|
||||
'source_label': 'Automatic Jump',
|
||||
'condition_type': 'django',
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def test_timeout(pub):
|
||||
|
@ -540,3 +563,69 @@ def test_conditional_jump_vs_tracing(pub):
|
|||
('register-comment', str(comment.id)),
|
||||
('jump', str(jump2.id)),
|
||||
]
|
||||
|
||||
|
||||
def test_timeout_tracing(pub, admin_user):
|
||||
workflow = Workflow(name='timeout')
|
||||
st1 = workflow.add_status('Status1', 'st1')
|
||||
st2 = workflow.add_status('Status2', 'st2')
|
||||
|
||||
jump = st1.add_action('timeout', id='_jump')
|
||||
jump.timeout = 30 * 60 # 30 minutes
|
||||
jump.status = 'st2'
|
||||
|
||||
add_message = st2.add_action('register-comment')
|
||||
add_message.comment = 'hello'
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'baz'
|
||||
formdef.fields = []
|
||||
formdef.workflow_id = workflow.id
|
||||
assert formdef.get_workflow().id == workflow.id
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
rewind(formdata, seconds=40 * 60)
|
||||
formdata.store()
|
||||
formdata.record_workflow_event('backoffice-created')
|
||||
_apply_timeouts(pub)
|
||||
|
||||
resp = login(get_app(pub), username='admin', password='admin').get(
|
||||
formdata.get_backoffice_url() + 'inspect'
|
||||
)
|
||||
assert [PyQuery(x).text() for x in resp.pyquery('#inspect-timeline li > *:nth-child(2)')] == [
|
||||
'Created (backoffice submission)',
|
||||
'Status1',
|
||||
'Timeout jump - Change Status on Timeout',
|
||||
'Status2',
|
||||
'History Message',
|
||||
]
|
||||
|
||||
|
||||
def test_jump_self_timeout(pub):
|
||||
FormDef.wipe()
|
||||
Workflow.wipe()
|
||||
|
||||
workflow = Workflow(name='timeout')
|
||||
st1 = workflow.add_status('Status1', 'st1')
|
||||
|
||||
jump = st1.add_action('jump')
|
||||
jump.timeout = 30 * 60 # 30 minutes
|
||||
jump.status = 'st1'
|
||||
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'baz'
|
||||
formdef.fields = []
|
||||
formdef.workflow_id = workflow.id
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
rewind(formdata, seconds=40 * 60)
|
||||
formdata.store()
|
||||
formdata.record_workflow_event('backoffice-created')
|
||||
_apply_timeouts(pub)
|
||||
|
|
|
@ -0,0 +1,440 @@
|
|||
import json
|
||||
|
||||
import pytest
|
||||
import responses
|
||||
from quixote import cleanup
|
||||
|
||||
from wcs import fields
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.wf.create_formdata import Mapping
|
||||
from wcs.workflow_traces import WorkflowTrace
|
||||
from wcs.workflows import Workflow
|
||||
|
||||
from ..utilities import clean_temporary_pub, create_temporary_pub
|
||||
|
||||
|
||||
def setup_module(module):
|
||||
cleanup()
|
||||
|
||||
|
||||
def teardown_module(module):
|
||||
clean_temporary_pub()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def pub(request):
|
||||
pub = create_temporary_pub()
|
||||
pub.cfg['language'] = {'language': 'en'}
|
||||
pub.write_cfg()
|
||||
req = HTTPRequest(None, {'SERVER_NAME': 'example.net', 'SCRIPT_NAME': ''})
|
||||
req.response.filter = {}
|
||||
req._user = None
|
||||
pub._set_request(req)
|
||||
pub.set_config(req)
|
||||
return pub
|
||||
|
||||
|
||||
def test_status_loop(pub):
|
||||
Workflow.wipe()
|
||||
|
||||
workflow = Workflow(name='foo')
|
||||
st1 = workflow.add_status(name='baz')
|
||||
st2 = workflow.add_status(name='bar')
|
||||
st1.loop_items_template = '{{ "abc"|make_list }}'
|
||||
st1.after_loop_status = str(st2.id)
|
||||
register_comment1 = st1.add_action('register-comment', id='_register-comment1')
|
||||
register_comment1.comment = 'foo {{ status_loop.items }} / {{ status_loop.current_item }} bar'
|
||||
register_comment2 = st1.add_action('register-comment', id='_register-comment2')
|
||||
register_comment2.comment = 'foo {{ status_loop.index }} / {{ status_loop.index0 }} bar'
|
||||
register_comment3 = st1.add_action('register-comment', id='_register-comment3')
|
||||
register_comment3.comment = 'foo {{ status_loop.first }} / {{ status_loop.last }} bar'
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'bar'
|
||||
formdef.fields = []
|
||||
formdef.workflow = workflow
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
assert formdata.status == 'wf-%s' % st2.id
|
||||
formdata.evolution[0]._display_parts = None # invalidate cache
|
||||
assert [str(x) for x in formdata.evolution[0].display_parts()] == [
|
||||
'<div>foo ['a', 'b', 'c'] / a bar</div>',
|
||||
'<div>foo 1 / 0 bar</div>',
|
||||
'<div>foo True / False bar</div>',
|
||||
'<div>foo ['a', 'b', 'c'] / b bar</div>',
|
||||
'<div>foo 2 / 1 bar</div>',
|
||||
'<div>foo False / False bar</div>',
|
||||
'<div>foo ['a', 'b', 'c'] / c bar</div>',
|
||||
'<div>foo 3 / 2 bar</div>',
|
||||
'<div>foo False / True bar</div>',
|
||||
]
|
||||
trace = WorkflowTrace.select_for_formdata(formdata)[0]
|
||||
assert trace.event == 'loop-start'
|
||||
assert trace.event_args == {}
|
||||
trace = WorkflowTrace.select_for_formdata(formdata)[-2]
|
||||
assert trace.event == 'loop-end'
|
||||
assert trace.event_args == {}
|
||||
|
||||
# add a conditional jump to stop the loop
|
||||
st3 = workflow.add_status(name='stop')
|
||||
jump = st1.add_action('jump', id='_jump')
|
||||
jump.status = st3.id
|
||||
jump.condition = {'type': 'django', 'value': 'status_loop.index0 == 1'}
|
||||
workflow.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
assert formdata.status == 'wf-%s' % st3.id
|
||||
formdata.evolution[0]._display_parts = None # invalidate cache
|
||||
assert [str(x) for x in formdata.evolution[0].display_parts()] == [
|
||||
'<div>foo ['a', 'b', 'c'] / a bar</div>',
|
||||
'<div>foo 1 / 0 bar</div>',
|
||||
'<div>foo True / False bar</div>',
|
||||
'<div>foo ['a', 'b', 'c'] / b bar</div>',
|
||||
'<div>foo 2 / 1 bar</div>',
|
||||
'<div>foo False / False bar</div>',
|
||||
]
|
||||
|
||||
|
||||
def test_status_loop_on_cards(pub):
|
||||
CardDef.wipe()
|
||||
Workflow.wipe()
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'foo'
|
||||
carddef.fields = []
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
for i in range(0, 2):
|
||||
carddata = carddef.data_class()()
|
||||
carddata.just_created()
|
||||
carddata.store()
|
||||
|
||||
workflow = Workflow(name='foo')
|
||||
st1 = workflow.add_status(name='baz')
|
||||
st2 = workflow.add_status(name='bar')
|
||||
st1.loop_items_template = '{{ cards|objects:"foo" }}'
|
||||
st1.after_loop_status = str(st2.id)
|
||||
register_comment = st1.add_action('register-comment', id='_register-comment')
|
||||
register_comment.comment = 'foo {{ status_loop.current_item.internal_id }} bar'
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'bar'
|
||||
formdef.fields = []
|
||||
formdef.workflow = workflow
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
assert formdata.status == 'wf-%s' % st2.id
|
||||
formdata.evolution[0]._display_parts = None # invalidate cache
|
||||
assert [str(x) for x in formdata.evolution[0].display_parts()] == [
|
||||
'<div>foo 1 bar</div>',
|
||||
'<div>foo 2 bar</div>',
|
||||
]
|
||||
|
||||
|
||||
def test_status_loop_on_wscall(pub):
|
||||
Workflow.wipe()
|
||||
|
||||
workflow = Workflow(name='foo')
|
||||
st0 = workflow.add_status(name='foo')
|
||||
wscall = st0.add_action('webservice_call', id='_wscall')
|
||||
wscall.url = 'http://test/'
|
||||
wscall.varname = 'wscall'
|
||||
st1 = workflow.add_status(name='baz')
|
||||
jump = st0.add_action('jump', id='_jump')
|
||||
jump.status = st1.id
|
||||
st2 = workflow.add_status(name='bar')
|
||||
st1.loop_items_template = '{{ form_workflow_data_wscall_response_result }}'
|
||||
st1.after_loop_status = str(st2.id)
|
||||
register_comment = st1.add_action('register-comment', id='_register-comment')
|
||||
register_comment.comment = 'foo {{ status_loop.current_item }} bar'
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'bar'
|
||||
formdef.fields = []
|
||||
formdef.workflow = workflow
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
with responses.RequestsMock() as rsps:
|
||||
rsps.get('http://test', status=200, json={'result': ['a', 'b', 'c', 'd']})
|
||||
formdata.perform_workflow()
|
||||
assert formdata.status == 'wf-%s' % st2.id
|
||||
formdata.evolution[1]._display_parts = None # invalidate cache
|
||||
assert [str(x) for x in formdata.evolution[1].display_parts()] == [
|
||||
'<div>foo a bar</div>',
|
||||
'<div>foo b bar</div>',
|
||||
'<div>foo c bar</div>',
|
||||
'<div>foo d bar</div>',
|
||||
]
|
||||
|
||||
|
||||
def test_status_loop_on_block(pub):
|
||||
Workflow.wipe()
|
||||
BlockDef.wipe()
|
||||
|
||||
block = BlockDef()
|
||||
block.name = 'Child'
|
||||
block.fields = [fields.StringField(id='123', required=True, label='First name', varname='firstname')]
|
||||
block.digest_template = '{{ child_var_firstname }}'
|
||||
block.store()
|
||||
|
||||
workflow = Workflow(name='foo')
|
||||
st1 = workflow.add_status(name='baz')
|
||||
st2 = workflow.add_status(name='bar')
|
||||
st1.loop_items_template = '{{ form_var_children }}'
|
||||
st1.after_loop_status = str(st2.id)
|
||||
register_comment = st1.add_action('register-comment', id='_register-comment')
|
||||
register_comment.comment = 'foo {{ status_loop.index }} {{ status_loop.current_item.firstname }} bar'
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'bar'
|
||||
formdef.fields = [fields.BlockField(id='1', label='Children', block_slug='child', varname='children')]
|
||||
formdef.workflow = workflow
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {
|
||||
'1': {
|
||||
'data': [{'123': 'first1'}, {'123': 'first2'}],
|
||||
'schema': {'123': 'string'},
|
||||
},
|
||||
'1_display': 'foo, bar',
|
||||
}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
assert formdata.status == 'wf-%s' % st2.id
|
||||
formdata.evolution[0]._display_parts = None # invalidate cache
|
||||
assert [str(x) for x in formdata.evolution[0].display_parts()] == [
|
||||
'<div>foo 1 first1 bar</div>',
|
||||
'<div>foo 2 first2 bar</div>',
|
||||
]
|
||||
|
||||
|
||||
def test_loop_on_block_create_carddata(pub):
|
||||
Workflow.wipe()
|
||||
BlockDef.wipe()
|
||||
CardDef.wipe()
|
||||
|
||||
block = BlockDef()
|
||||
block.name = 'foobar'
|
||||
block.digest_template = 'X{{ foobar_var_foo }}Y'
|
||||
block.fields = [
|
||||
fields.StringField(id='123', required=True, label='Test', varname='foo'),
|
||||
fields.StringField(id='234', required=True, label='Test2', varname='bar'),
|
||||
]
|
||||
block.store()
|
||||
|
||||
carddef = CardDef()
|
||||
carddef.name = 'Foo Card'
|
||||
carddef.fields = [
|
||||
fields.StringField(id='0', label='foo', varname='foo'),
|
||||
fields.StringField(id='1', label='bar', varname='bar'),
|
||||
]
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
|
||||
workflow = Workflow(name='foo')
|
||||
st1 = workflow.add_status(name='baz')
|
||||
st2 = workflow.add_status(name='bar')
|
||||
st1.loop_items_template = '{{ form_var_foobars }}'
|
||||
st1.after_loop_status = str(st2.id)
|
||||
create = st1.add_action('create_carddata', id='_create')
|
||||
create.formdef_slug = carddef.url_name
|
||||
create.mappings = [
|
||||
Mapping(field_id='0', expression='{{ status_loop.current_item.foo }}'),
|
||||
Mapping(field_id='1', expression='{{ status_loop.current_item.bar }}'),
|
||||
]
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'bar'
|
||||
formdef.fields = [fields.BlockField(id='1', label='Foobar', block_slug='foobar', varname='foobars')]
|
||||
formdef.workflow = workflow
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {
|
||||
'1': {
|
||||
'data': [{'123': 'foo-111', '234': 'bar-111'}, {'123': 'foo-222', '234': 'bar-222'}],
|
||||
'schema': {'123': 'string'},
|
||||
},
|
||||
'1_display': 'foo, bar',
|
||||
}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
assert formdata.status == 'wf-%s' % st2.id
|
||||
|
||||
assert carddef.data_class().count() == 2
|
||||
new_carddata = carddef.data_class().select(order_by='id')[0]
|
||||
assert new_carddata.data == {
|
||||
'0': 'foo-111',
|
||||
'1': 'bar-111',
|
||||
}
|
||||
new_carddata = carddef.data_class().select(order_by='id')[1]
|
||||
assert new_carddata.data == {
|
||||
'0': 'foo-222',
|
||||
'1': 'bar-222',
|
||||
}
|
||||
|
||||
|
||||
def test_status_loop_on_items(pub):
|
||||
CardDef.wipe()
|
||||
Workflow.wipe()
|
||||
|
||||
workflow = Workflow(name='foo')
|
||||
st1 = workflow.add_status(name='baz')
|
||||
st2 = workflow.add_status(name='bar')
|
||||
st1.loop_items_template = '{{ form_var_items }}'
|
||||
st1.after_loop_status = str(st2.id)
|
||||
register_comment = st1.add_action('register-comment', id='_register-comment')
|
||||
register_comment.comment = 'foo {{ status_loop.index }} {{ status_loop.current_item }} bar'
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'bar'
|
||||
formdef.fields = [
|
||||
fields.ItemsField(id='1', label='Items', varname='items', items=['foo1', 'foo2', 'foo3'])
|
||||
]
|
||||
formdef.workflow = workflow
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {
|
||||
'1': ['foo1', 'foo3'],
|
||||
}
|
||||
formdata.data['1_display'] = formdef.fields[0].store_display_value(formdata.data, '1')
|
||||
formdata.data['1_structured'] = formdef.fields[0].store_structured_value(formdata.data, '1')
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
assert formdata.status == 'wf-%s' % st2.id
|
||||
formdata.evolution[0]._display_parts = None # invalidate cache
|
||||
assert [str(x) for x in formdata.evolution[0].display_parts()] == [
|
||||
'<div>foo 1 foo1 bar</div>',
|
||||
'<div>foo 2 foo3 bar</div>',
|
||||
]
|
||||
|
||||
# with carddef datasource
|
||||
carddef = CardDef()
|
||||
carddef.name = 'foo'
|
||||
carddef.fields = [fields.StringField(id='1', label='First name', varname='firstname')]
|
||||
carddef.store()
|
||||
carddef.data_class().wipe()
|
||||
for i in range(0, 3):
|
||||
carddata = carddef.data_class()()
|
||||
carddata.data = {
|
||||
'1': 'foo%s' % i,
|
||||
}
|
||||
carddata.just_created()
|
||||
carddata.store()
|
||||
|
||||
formdef.fields[0].data_source = {'type': 'carddef:%s' % carddef.url_name}
|
||||
formdef.fields[0].items = None
|
||||
formdef.store()
|
||||
st1.loop_items_template = '{{ form_var_items }}'
|
||||
register_comment.comment = 'foo {{ status_loop.index }} {{ status_loop.current_item.var.firstname }} bar'
|
||||
workflow.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {
|
||||
'1': ['1', '3'],
|
||||
}
|
||||
formdata.data['1_display'] = formdef.fields[0].store_display_value(formdata.data, '1')
|
||||
formdata.data['1_structured'] = formdef.fields[0].store_structured_value(formdata.data, '1')
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
assert formdata.status == 'wf-%s' % st2.id
|
||||
formdata.evolution[0]._display_parts = None # invalidate cache
|
||||
assert [str(x) for x in formdata.evolution[0].display_parts()] == [
|
||||
'<div>foo 1 foo0 bar</div>',
|
||||
'<div>foo 2 foo2 bar</div>',
|
||||
]
|
||||
|
||||
# with json value
|
||||
datasource = {
|
||||
'type': 'jsonvalue',
|
||||
'value': json.dumps(
|
||||
[
|
||||
{'id': 'b', 'text': 'baker', 'extra': 'plop'},
|
||||
{'id': 'c', 'text': 'cook', 'extra': 'plop2'},
|
||||
{'id': 'l', 'text': 'lawyer', 'extra': 'plop3'},
|
||||
]
|
||||
),
|
||||
}
|
||||
formdef.fields[0].data_source = datasource
|
||||
formdef.store()
|
||||
st1.loop_items_template = '{{ form_var_items }}'
|
||||
register_comment.comment = 'foo {{ status_loop.index }} {{ status_loop.current_item.extra }} bar'
|
||||
workflow.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {
|
||||
'1': ['b', 'l'],
|
||||
}
|
||||
formdata.data['1_display'] = formdef.fields[0].store_display_value(formdata.data, '1')
|
||||
formdata.data['1_structured'] = formdef.fields[0].store_structured_value(formdata.data, '1')
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
formdata.perform_workflow()
|
||||
assert formdata.status == 'wf-%s' % st2.id
|
||||
formdata.evolution[0]._display_parts = None # invalidate cache
|
||||
assert [str(x) for x in formdata.evolution[0].display_parts()] == [
|
||||
'<div>foo 1 plop bar</div>',
|
||||
'<div>foo 2 plop3 bar</div>',
|
||||
]
|
||||
|
||||
|
||||
def test_status_loop_unknown_status_with_global_action(pub):
|
||||
workflow = Workflow(name='foo')
|
||||
st1 = workflow.add_status(name='baz')
|
||||
st2 = workflow.add_status(name='bar')
|
||||
st1.loop_items_template = '{{ "abc"|make_list }}'
|
||||
st1.after_loop_status = str(st2.id)
|
||||
ac1 = workflow.add_global_action('Action', 'ac1')
|
||||
ac1.backoffice_info_text = '<p>Foo</p>'
|
||||
add_to_journal = ac1.add_action('register-comment', id='_add_to_journal')
|
||||
add_to_journal.comment = 'HELLO WORLD'
|
||||
trigger = ac1.triggers[0]
|
||||
assert trigger.key == 'manual'
|
||||
trigger.roles = ['_submitter']
|
||||
trigger.statuses = ['unknown']
|
||||
workflow.store()
|
||||
|
||||
formdef = FormDef()
|
||||
formdef.name = 'bar'
|
||||
formdef.fields = []
|
||||
formdef.workflow = workflow
|
||||
formdef.store()
|
||||
|
||||
user = pub.user_class(name='admin')
|
||||
user.email = 'admin@localhost'
|
||||
user.store()
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.status = 'unknown'
|
||||
formdata.user_id = str(user.id)
|
||||
formdata.store()
|
||||
formdata.perform_global_action(ac1.id, user) # no error
|
|
@ -7,7 +7,8 @@ import pytest
|
|||
import responses
|
||||
from quixote import cleanup, get_publisher
|
||||
|
||||
from wcs.fields import BoolField, FileField, ItemField, ItemsField, StringField
|
||||
from wcs.blocks import BlockDef
|
||||
from wcs.fields import BlockField, BoolField, FileField, ItemField, ItemsField, StringField
|
||||
from wcs.formdef import FormDef
|
||||
from wcs.qommon.http_request import HTTPRequest
|
||||
from wcs.wf.wscall import JournalWsCallErrorPart, WebserviceCallStatusItem
|
||||
|
@ -922,7 +923,7 @@ def test_webservice_target_status(pub):
|
|||
assert targets.count(status2) == 2
|
||||
|
||||
|
||||
def test_webservice_with_complex_data(http_requests, pub):
|
||||
def test_webservice_with_complex_data_in_payload(http_requests, pub):
|
||||
pub.substitutions.feed(MockSubstitutionVariables())
|
||||
|
||||
wf = Workflow(name='wf1')
|
||||
|
@ -1044,3 +1045,116 @@ def test_webservice_with_complex_data(http_requests, pub):
|
|||
assert http_requests.get_last('method') == 'POST'
|
||||
payload = json.loads(http_requests.get_last('body'))
|
||||
assert payload['bool'] is False
|
||||
|
||||
|
||||
def test_webservice_with_complex_data_in_query_string(http_requests, pub):
|
||||
pub.substitutions.feed(MockSubstitutionVariables())
|
||||
|
||||
wf = Workflow(name='wf1')
|
||||
wf.add_status('Status1', 'st1')
|
||||
wf.add_status('StatusErr', 'sterr')
|
||||
wf.store()
|
||||
|
||||
datasource = {
|
||||
'type': 'jsonvalue',
|
||||
'value': json.dumps(
|
||||
[
|
||||
{'id': 'a', 'text': 'aa', 'more': 'aaa'},
|
||||
{'id': 'b', 'text': 'bb', 'more': 'bbb'},
|
||||
{'id': 'c', 'text': 'cc', 'more': 'ccc'},
|
||||
]
|
||||
),
|
||||
}
|
||||
|
||||
BlockDef.wipe()
|
||||
block = BlockDef()
|
||||
block.name = 'foobar'
|
||||
block.fields = [StringField(id='1', label='String', varname='string')]
|
||||
block.store()
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'baz'
|
||||
formdef.fields = [
|
||||
ItemField(id='1', label='1st field', varname='item', data_source=datasource),
|
||||
ItemsField(id='2', label='2nd field', varname='items', data_source=datasource),
|
||||
StringField(id='3', label='3rd field', varname='str'),
|
||||
StringField(id='4', label='4th field', varname='empty_str'),
|
||||
StringField(id='5', label='5th field', varname='none'),
|
||||
BoolField(id='6', label='6th field', varname='bool'),
|
||||
BlockField(id='7', label='7th field', varname='block', block_slug=block.slug, max_items=3),
|
||||
]
|
||||
formdef.workflow_id = wf.id
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.data = {}
|
||||
formdata.data['1'] = 'a'
|
||||
formdata.data['1_display'] = 'aa'
|
||||
formdata.data['1_structured'] = formdef.fields[0].store_structured_value(formdata.data, '1')
|
||||
formdata.data['2'] = ['a', 'b']
|
||||
formdata.data['2_display'] = 'aa, bb'
|
||||
formdata.data['2_structured'] = formdef.fields[1].store_structured_value(formdata.data, '2')
|
||||
formdata.data['3'] = 'tutuche'
|
||||
formdata.data['4'] = 'empty_str'
|
||||
formdata.data['5'] = None
|
||||
formdata.data['6'] = False
|
||||
formdata.data['7'] = {
|
||||
'data': [
|
||||
{
|
||||
'1': 'plop',
|
||||
},
|
||||
{
|
||||
'1': 'poulpe',
|
||||
},
|
||||
],
|
||||
'schema': {},
|
||||
}
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
item = WebserviceCallStatusItem()
|
||||
item.method = 'POST'
|
||||
item.url = 'http://remote.example.net'
|
||||
item.qs_data = {
|
||||
'item': '{{ form_var_item }}',
|
||||
'items': '{{ form_var_items }}',
|
||||
'item_raw': '{{ form_var_item_raw }}',
|
||||
'items_raw': '{{ form_var_items_raw }}',
|
||||
'with_items_raw': '{% with x=form_var_items_raw %}{{ x }}{% endwith %}',
|
||||
'with_items_upper': '{% with x=form_var_items_raw %}{{ x.1|upper }}{% endwith %}',
|
||||
'joined_items_raw': '{{ form_var_items_raw|join:"|" }}',
|
||||
'forloop_items_raw': '{% for item in form_var_items_raw %}{{item}}|{% endfor %}',
|
||||
'str': '{{ form_var_str }}',
|
||||
'str_mod': '{{ form_var_str }}--plop',
|
||||
'int': '{{ 1000 }}',
|
||||
'decimal': '{{ "1000"|decimal }}',
|
||||
'decimal2': '{{ "1000.1"|decimal }}',
|
||||
'empty_string': '{{ form_var_empty }}',
|
||||
'none': '{{ form_var_none }}',
|
||||
'bool': '{{ form_var_bool_raw }}',
|
||||
'time': '{{ "13:12"|time }}',
|
||||
'block_template': '{% for b in form_var_block %}{{ b.string }}{% endfor %}',
|
||||
}
|
||||
pub.substitutions.feed(formdata)
|
||||
item.perform(formdata)
|
||||
assert sorted(urllib.parse.parse_qsl(urllib.parse.urlparse(http_requests.get_last('url')).query)) == [
|
||||
('block_template', 'ploppoulpe'),
|
||||
('bool', 'False'),
|
||||
('decimal', '1E+3'),
|
||||
('decimal2', '1000.1'),
|
||||
('forloop_items_raw', 'a|b|'),
|
||||
('int', '1000'),
|
||||
('item', 'aa'),
|
||||
('item_raw', 'a'),
|
||||
('items', 'aa, bb'),
|
||||
('items_raw', 'a'),
|
||||
('items_raw', 'b'),
|
||||
('joined_items_raw', 'a|b'),
|
||||
('str', 'tutuche'),
|
||||
('str_mod', 'tutuche--plop'),
|
||||
('time', '13:12:00'),
|
||||
('with_items_raw', 'a'),
|
||||
('with_items_raw', 'b'),
|
||||
('with_items_upper', 'B'),
|
||||
]
|
||||
|
|
1
tox.ini
1
tox.ini
|
@ -12,6 +12,7 @@ setenv =
|
|||
LC_ALL=C
|
||||
LC_TIME=C
|
||||
LANG=C
|
||||
JOB_NAME={env:JOB_NAME:}
|
||||
coverage: COVERAGE=--cov-report xml --cov-report html --cov=wcs/ --cov-config .coveragerc -v
|
||||
passenv =
|
||||
USER
|
||||
|
|
|
@ -54,6 +54,7 @@ class BlockDirectory(FieldsDirectory):
|
|||
'inspect',
|
||||
'duplicate',
|
||||
('history', 'snapshots_dir'),
|
||||
'overwrite',
|
||||
]
|
||||
field_def_page_class = BlockFieldDefPage
|
||||
blacklisted_types = ['page', 'table', 'table-select', 'tablerows', 'ranked-items', 'blocks', 'computed']
|
||||
|
@ -106,6 +107,14 @@ class BlockDirectory(FieldsDirectory):
|
|||
r = TemplateIO(html=True)
|
||||
r += htmltext('<div id="appbar">')
|
||||
r += htmltext('<h2>%s</h2>') % self.objectdef.name
|
||||
r += htmltext('<span class="actions">')
|
||||
r += htmltext('<a class="extra-actions-menu-opener"></a>')
|
||||
r += htmltext('<ul class="extra-actions-menu">')
|
||||
r += htmltext('<li><a href="export">%s</a></li>') % _('Export')
|
||||
r += htmltext('<li><a href="delete" rel="popup">%s</a></li>') % _('Delete')
|
||||
r += htmltext('</ul>')
|
||||
r += htmltext('<a href="settings" rel="popup" role="button">%s</a>') % _('Settings')
|
||||
r += htmltext('</span>')
|
||||
r += htmltext('</div>')
|
||||
r += utils.last_modification_block(obj=self.objectdef)
|
||||
r += get_session().display_message()
|
||||
|
@ -132,17 +141,25 @@ class BlockDirectory(FieldsDirectory):
|
|||
|
||||
def get_new_field_form_sidebar(self, page_id):
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<ul id="sidebar-actions">')
|
||||
r += htmltext('<li><a href="delete" rel="popup">%s</a></li>') % _('Delete')
|
||||
r += htmltext('<li><a href="duplicate" rel="popup">%s</a></li>') % _('Duplicate')
|
||||
r += htmltext('<li><a href="export">%s</a></li>') % _('Export')
|
||||
if get_publisher().snapshot_class:
|
||||
r += htmltext('<li><a rel="popup" href="history/save">%s</a></li>') % _('Save snapshot')
|
||||
r += htmltext('<li><a href="history/">%s</a></li>') % _('History')
|
||||
r += htmltext('<li><a href="inspect">%s</a></li>') % _('Inspector')
|
||||
r += htmltext('<li><a href="settings" rel="popup">%s</a></li>') % _('Settings')
|
||||
r += htmltext('</ul>')
|
||||
r += super().get_new_field_form_sidebar(page_id=page_id)
|
||||
r += htmltext('<h3>%s</h3>') % _('Actions')
|
||||
r += htmltext('<ul class="sidebar--buttons">')
|
||||
r += htmltext('<li><a class="button button-paragraph" href="duplicate" rel="popup">%s</a>') % _(
|
||||
'Duplicate'
|
||||
)
|
||||
if get_publisher().snapshot_class:
|
||||
r += htmltext('<li><a class="button button-paragraph" href="history/save">%s</a>') % _(
|
||||
'Save snapshot'
|
||||
)
|
||||
r += htmltext('<li><a class="button button-paragraph" rel="popup" href="overwrite">%s</a>') % _(
|
||||
'Overwrite with new import'
|
||||
)
|
||||
r += htmltext('</ul>')
|
||||
r += htmltext('<h3>%s</h3>') % _('Navigation')
|
||||
r += htmltext('<ul class="sidebar--buttons">')
|
||||
r += htmltext('<li><a class="button button-paragraph" href="history/">%s</a></li>') % _('History')
|
||||
r += htmltext('<li><a class="button button-paragraph" href="inspect">%s</a></li>') % _('Inspector')
|
||||
r += htmltext('</ul>')
|
||||
return r.getvalue()
|
||||
|
||||
def delete(self):
|
||||
|
@ -209,10 +226,45 @@ class BlockDirectory(FieldsDirectory):
|
|||
content_type='application/x-wcs-form',
|
||||
)
|
||||
|
||||
def overwrite(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
form.widgets.append(
|
||||
HtmlWidget(
|
||||
'<div class="warningnotice"><p>%s</p></div>'
|
||||
% _('Field data will be lost if overwriting with an incompatible block.')
|
||||
)
|
||||
)
|
||||
form.add(FileWidget, 'file', title=_('File'), required=True)
|
||||
form.add_submit('submit', _('Submit'))
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('.')
|
||||
|
||||
if form.is_submitted() and not form.has_errors():
|
||||
try:
|
||||
return self.overwrite_submit(form)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
get_response().breadcrumb.append(('overwrite', _('Overwrite')))
|
||||
get_response().set_title(title=_('Overwrite'))
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<h2>%s</h2>') % _('Overwrite')
|
||||
r += form.render()
|
||||
return r.getvalue()
|
||||
|
||||
def overwrite_submit(self, form):
|
||||
blockdef = BlocksDirectory.import_blockdef(form)
|
||||
self.objectdef.name = blockdef.name
|
||||
self.objectdef.digest_template = blockdef.digest_template
|
||||
self.objectdef.fields = blockdef.fields
|
||||
self.objectdef.store(comment=_('Overwritten'))
|
||||
return redirect('.')
|
||||
|
||||
def settings(self):
|
||||
get_response().breadcrumb.append(('settings', _('Settings')))
|
||||
form = Form()
|
||||
form.add(StringWidget, 'name', title=_('Name'), value=self.objectdef.name, size=50)
|
||||
form.add(StringWidget, 'name', title=_('Name'), value=self.objectdef.name, size=50, required=True)
|
||||
disabled_slug = bool(self.objectdef.is_used())
|
||||
widget = form.add(
|
||||
SlugWidget,
|
||||
|
@ -307,7 +359,7 @@ class BlocksDirectory(Directory):
|
|||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.applications_dir = ApplicationsDirectory(BlockDef.xml_root_node)
|
||||
self.applications_dir = ApplicationsDirectory(BlockDef)
|
||||
|
||||
def _q_traverse(self, path):
|
||||
if not get_publisher().get_backoffice_root().is_global_accessible('forms'):
|
||||
|
@ -330,6 +382,7 @@ class BlocksDirectory(Directory):
|
|||
context = {
|
||||
'view': self,
|
||||
'applications': Application.select_for_object_type(BlockDef.xml_root_node),
|
||||
'elements_label': BlockDef.verbose_name_plural,
|
||||
'has_sidebar': True,
|
||||
}
|
||||
blocks = BlockDef.select(order_by='name')
|
||||
|
@ -408,7 +461,8 @@ class BlocksDirectory(Directory):
|
|||
r += form.render()
|
||||
return r.getvalue()
|
||||
|
||||
def import_submit(self, form):
|
||||
@classmethod
|
||||
def import_blockdef(cls, form):
|
||||
fp = form.get_widget('file').parse().fp
|
||||
|
||||
error, reason = False, None
|
||||
|
@ -432,6 +486,10 @@ class BlocksDirectory(Directory):
|
|||
form.set_error('file', msg)
|
||||
raise ValueError()
|
||||
|
||||
return blockdef
|
||||
|
||||
def import_submit(self, form):
|
||||
blockdef = self.import_blockdef(form)
|
||||
initial_blockdef_name = blockdef.name
|
||||
blockdef_names = [x.name for x in BlockDef.select()]
|
||||
copy_no = 1
|
||||
|
|
|
@ -384,7 +384,7 @@ class CategoriesDirectory(Directory):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.applications_dir = ApplicationsDirectory(self.category_class.xml_root_node)
|
||||
self.applications_dir = ApplicationsDirectory(self.category_class)
|
||||
|
||||
def _q_index(self):
|
||||
from wcs.applications import Application
|
||||
|
@ -400,6 +400,7 @@ class CategoriesDirectory(Directory):
|
|||
'view': self,
|
||||
'categories': categories,
|
||||
'applications': Application.select_for_object_type(self.category_class.xml_root_node),
|
||||
'elements_label': self.category_class.verbose_name_plural,
|
||||
'has_sidebar': True,
|
||||
},
|
||||
is_django_native=True,
|
||||
|
@ -415,7 +416,7 @@ class CategoriesDirectory(Directory):
|
|||
new_order = [o for o in new_order if o in categories_by_id]
|
||||
for i, o in enumerate(new_order):
|
||||
categories_by_id[o].position = i + 1
|
||||
categories_by_id[o].store()
|
||||
categories_by_id[o].store(store_snapshot=False)
|
||||
return 'ok'
|
||||
|
||||
def new(self):
|
||||
|
|
|
@ -46,7 +46,7 @@ class CommentTemplatesDirectory(Directory):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.applications_dir = ApplicationsDirectory(CommentTemplate.xml_root_node)
|
||||
self.applications_dir = ApplicationsDirectory(CommentTemplate)
|
||||
|
||||
def _q_traverse(self, path):
|
||||
if not get_publisher().get_backoffice_root().is_global_accessible('workflows'):
|
||||
|
@ -67,6 +67,7 @@ class CommentTemplatesDirectory(Directory):
|
|||
context = {
|
||||
'view': self,
|
||||
'applications': Application.select_for_object_type(CommentTemplate.xml_root_node),
|
||||
'elements_label': CommentTemplate.verbose_name_plural,
|
||||
'has_sidebar': True,
|
||||
}
|
||||
context.update(self.get_list_context(comment_templates))
|
||||
|
|
|
@ -27,6 +27,7 @@ from wcs.categories import CardDefCategory, DataSourceCategory
|
|||
from wcs.data_sources import (
|
||||
DataSourceSelectionWidget,
|
||||
NamedDataSource,
|
||||
NamedDataSourceImportError,
|
||||
RefreshAgendas,
|
||||
get_structured_items,
|
||||
has_chrono,
|
||||
|
@ -388,7 +389,7 @@ class NamedDataSourcePage(Directory):
|
|||
def preview_block(self):
|
||||
get_request().disable_error_notifications = True
|
||||
get_request().ignore_session = True
|
||||
get_response().filter = {'raw': True}
|
||||
get_response().raw = True
|
||||
data_source = self.datasource.extended_data_source
|
||||
try:
|
||||
items = get_structured_items(data_source)
|
||||
|
@ -521,7 +522,7 @@ class NamedDataSourcesDirectory(Directory):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.applications_dir = ApplicationsDirectory(NamedDataSource.xml_root_node)
|
||||
self.applications_dir = ApplicationsDirectory(NamedDataSource)
|
||||
|
||||
def _q_traverse(self, path):
|
||||
if (
|
||||
|
@ -543,6 +544,7 @@ class NamedDataSourcesDirectory(Directory):
|
|||
'has_chrono': has_chrono(get_publisher()),
|
||||
'has_users': True,
|
||||
'applications': Application.select_for_object_type(NamedDataSource.xml_root_node),
|
||||
'elements_label': NamedDataSource.verbose_name_plural,
|
||||
'has_sidebar': True,
|
||||
}
|
||||
data_sources = NamedDataSource.select(order_by='name')
|
||||
|
@ -554,7 +556,7 @@ class NamedDataSourcesDirectory(Directory):
|
|||
is_django_native=True,
|
||||
)
|
||||
|
||||
def get_list_context(self, objects, application=None):
|
||||
def get_list_context(self, objects, application=Ellipsis):
|
||||
from wcs.applications import Application
|
||||
|
||||
data_sources = []
|
||||
|
@ -575,8 +577,11 @@ class NamedDataSourcesDirectory(Directory):
|
|||
category.data_sources = [x for x in data_sources if x.category_id == category.id]
|
||||
generated_data_sources = list(CardDef.get_carddefs_as_data_source())
|
||||
generated_data_sources.sort(key=lambda x: misc.simplify(x[1]))
|
||||
if application:
|
||||
carddefs = application.get_objects_for_object_type(CardDef.xml_root_node, lightweight=True)
|
||||
if application is None:
|
||||
carddefs = Application.get_orphan_objects_for_object_type(CardDef.xml_root_node)
|
||||
generated_data_sources = [g for g in generated_data_sources if g[0] in carddefs]
|
||||
elif application is not Ellipsis:
|
||||
carddefs = application.get_objects_for_object_type(CardDef.xml_root_node)
|
||||
generated_data_sources = [g for g in generated_data_sources if g[0] in carddefs]
|
||||
else:
|
||||
Application.populate_objects([g[0] for g in generated_data_sources])
|
||||
|
@ -663,15 +668,22 @@ class NamedDataSourcesDirectory(Directory):
|
|||
def import_submit(self, form):
|
||||
fp = form.get_widget('file').parse().fp
|
||||
|
||||
error = False
|
||||
error, reason = False, None
|
||||
try:
|
||||
datasource = NamedDataSource.import_from_xml(fp)
|
||||
get_session().message = ('info', _('This datasource has been successfully imported.'))
|
||||
except NamedDataSourceImportError as e:
|
||||
error = True
|
||||
reason = str(e)
|
||||
except ValueError:
|
||||
error = True
|
||||
|
||||
if error:
|
||||
form.set_error('file', _('Invalid File'))
|
||||
if reason:
|
||||
msg = _('Invalid File (%s)') % reason
|
||||
else:
|
||||
msg = _('Invalid File')
|
||||
form.set_error('file', msg)
|
||||
raise ValueError()
|
||||
|
||||
try:
|
||||
|
|
|
@ -105,6 +105,12 @@ class FieldDefPage(Directory):
|
|||
)
|
||||
else:
|
||||
r += htmltext('<h3 class="field-edit--subtitle">%s</h3>') % self.field.description
|
||||
existing_varnames = {
|
||||
x.varname for x in self.objectdef.fields if x.varname if x.id != self.field.id
|
||||
}
|
||||
r += htmltext(
|
||||
'<script id="other-fields-varnames">%s</script>' % json.dumps(list(existing_varnames))
|
||||
)
|
||||
for widget in form.widgets:
|
||||
if hasattr(widget, 'get_widget'):
|
||||
add_element_widget = widget.get_widget('add_element')
|
||||
|
@ -190,7 +196,20 @@ class FieldDefPage(Directory):
|
|||
to_be_deleted.reverse()
|
||||
# add delete_fields checkbox only if the page has fields
|
||||
if to_be_deleted:
|
||||
form.add(CheckboxWidget, 'delete_fields', title=_('Also remove all fields from the page'))
|
||||
form.add(
|
||||
CheckboxWidget,
|
||||
'delete_fields',
|
||||
title=_('Also remove all fields from the page'),
|
||||
attrs={'data-dynamic-display-parent': 'true'},
|
||||
)
|
||||
form.widgets.append(
|
||||
HtmlWidget(
|
||||
'<div class="warningnotice" '
|
||||
'data-dynamic-display-child-of="delete_fields" '
|
||||
'data-dynamic-display-checked="true">%s</div>'
|
||||
% _('Warning: the page fields data will be permanently deleted.')
|
||||
)
|
||||
)
|
||||
form.add_submit('delete', _('Delete'))
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
if form.get_widget('cancel').parse():
|
||||
|
@ -325,7 +344,12 @@ class FieldsDirectory(Directory):
|
|||
|
||||
def _q_traverse(self, path):
|
||||
if self.page_id:
|
||||
get_response().breadcrumb.append(('pages/%s/' % self.page_id, _('Page')))
|
||||
try:
|
||||
page_field = [x for x in self.objectdef.fields if x.id == self.page_id][0]
|
||||
except IndexError:
|
||||
raise errors.TraversalError()
|
||||
label = misc.ellipsize(page_field.unhtmled_label, 40)
|
||||
get_response().breadcrumb.append(('pages/%s/' % self.page_id, _('Page "%s"') % label))
|
||||
else:
|
||||
get_response().breadcrumb.append(('fields/', _('Fields')))
|
||||
return Directory._q_traverse(self, path)
|
||||
|
@ -516,8 +540,8 @@ class FieldsDirectory(Directory):
|
|||
)
|
||||
return r.getvalue()
|
||||
|
||||
r += htmltext('<div id="new-field">')
|
||||
r += htmltext('<h3>%s</h3>') % _('New Field')
|
||||
r += htmltext('<div id="new-field">')
|
||||
get_request().form = None # ignore the eventual ?page=x
|
||||
form = self.get_new_field_form(page_id)
|
||||
r += form.render()
|
||||
|
|
|
@ -30,6 +30,7 @@ from wcs.carddef import CardDef
|
|||
from wcs.categories import Category
|
||||
from wcs.formdef import (
|
||||
DRAFTS_DEFAULT_LIFESPAN,
|
||||
DRAFTS_DEFAULT_MAX_PER_USER,
|
||||
FormDef,
|
||||
FormdefImportError,
|
||||
FormdefImportRecoverableError,
|
||||
|
@ -290,6 +291,23 @@ class OptionsDirectory(Directory):
|
|||
widget.validation_function = check_lifespan
|
||||
widget.validation_function_error_message = _('Lifespan must be between 2 and 100 days.')
|
||||
|
||||
widget = form.add(
|
||||
WcsExtraStringWidget,
|
||||
'drafts_max_per_user',
|
||||
title=_('Maximum number of drafts per user (between 2 and 100)'),
|
||||
value=self.formdef.drafts_max_per_user,
|
||||
hint=_('%s drafts per user by default') % DRAFTS_DEFAULT_MAX_PER_USER,
|
||||
)
|
||||
|
||||
def check_max_per_user(value):
|
||||
try:
|
||||
return bool(int(value) >= 2 and int(value) <= 100)
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
widget.validation_function = check_max_per_user
|
||||
widget.validation_function_error_message = _('Maximum must be between 2 and 100 drafts.')
|
||||
|
||||
form.widgets.append(HtmlWidget(htmltext('<h3>%s</h3>') % _('Tracking Code')))
|
||||
form.add(
|
||||
CheckboxWidget,
|
||||
|
@ -332,10 +350,12 @@ class OptionsDirectory(Directory):
|
|||
def management(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
form.add(
|
||||
CheckboxWidget,
|
||||
'include_download_all_button',
|
||||
title=_('Include button to download all files'),
|
||||
value=self.formdef.include_download_all_button,
|
||||
CheckboxesWidget,
|
||||
'management_sidebar_items',
|
||||
title=_('Sidebar elements'),
|
||||
options=[(x[0], x[1], x[0]) for x in self.formdef.get_management_sidebar_available_items()],
|
||||
value=self.formdef.get_management_sidebar_items(),
|
||||
inline=False,
|
||||
)
|
||||
form.add(
|
||||
CheckboxWidget,
|
||||
|
@ -493,7 +513,9 @@ class OptionsDirectory(Directory):
|
|||
'id_template',
|
||||
'submission_lateral_template',
|
||||
'drafts_lifespan',
|
||||
'drafts_max_per_user',
|
||||
'user_support',
|
||||
'management_sidebar_items',
|
||||
]
|
||||
for attr in attrs:
|
||||
widget = form.get_widget(attr)
|
||||
|
@ -503,6 +525,10 @@ class OptionsDirectory(Directory):
|
|||
if has_error:
|
||||
continue
|
||||
new_value = widget.parse()
|
||||
if attr == 'management_sidebar_items':
|
||||
new_value = set(new_value)
|
||||
if new_value == self.formdef.__class__.management_sidebar_items:
|
||||
new_value = {'__default__'}
|
||||
if attr == 'digest_template':
|
||||
if self.formdef.default_digest_template != new_value:
|
||||
self.changed = True
|
||||
|
@ -777,7 +803,10 @@ class FormDefPage(Directory, TempfileDirectoryMixin):
|
|||
'options/management',
|
||||
_('Management'),
|
||||
_('Custom')
|
||||
if (self.formdef.skip_from_360_view or self.formdef.include_download_all_button)
|
||||
if (
|
||||
self.formdef.skip_from_360_view
|
||||
or self.formdef.management_sidebar_items != {'__default__'}
|
||||
)
|
||||
else _('Default'),
|
||||
),
|
||||
'tracking_code': self.add_option_line(
|
||||
|
@ -1140,7 +1169,12 @@ class FormDefPage(Directory, TempfileDirectoryMixin):
|
|||
# there are existing formdata, status will have to be mapped
|
||||
return redirect('workflow-status-remapping?new=%s' % workflow_id)
|
||||
|
||||
job = WorkflowChangeJob(formdef=self.formdef, new_workflow_id=workflow_id, status_mapping={})
|
||||
job = WorkflowChangeJob(
|
||||
formdef=self.formdef,
|
||||
new_workflow_id=workflow_id,
|
||||
status_mapping={},
|
||||
user_id=get_session().user,
|
||||
)
|
||||
job.store()
|
||||
get_response().add_after_job(job)
|
||||
return redirect(job.get_processing_url())
|
||||
|
@ -1230,7 +1264,10 @@ class FormDefPage(Directory, TempfileDirectoryMixin):
|
|||
return self.workflow_status_remapping()
|
||||
|
||||
job = WorkflowChangeJob(
|
||||
formdef=self.formdef, new_workflow_id=new_workflow.id, status_mapping=status_mapping
|
||||
formdef=self.formdef,
|
||||
new_workflow_id=new_workflow.id,
|
||||
status_mapping=status_mapping,
|
||||
user_id=get_session().user,
|
||||
)
|
||||
job.store()
|
||||
get_response().add_after_job(job)
|
||||
|
@ -1309,12 +1346,21 @@ class FormDefPage(Directory, TempfileDirectoryMixin):
|
|||
return self.duplicate_submit(form)
|
||||
|
||||
def duplicate_submit(self, form):
|
||||
from wcs.testdef import TestDef
|
||||
|
||||
testdefs = TestDef.select_for_objectdef(self.formdefui.formdef)
|
||||
|
||||
self.formdefui.formdef.name = form.get_widget('name').parse()
|
||||
self.formdefui.formdef.id = None
|
||||
self.formdefui.formdef.url_name = None
|
||||
self.formdefui.formdef.table_name = None
|
||||
self.formdefui.formdef.disabled = True
|
||||
self.formdefui.formdef.store()
|
||||
|
||||
for testdef in testdefs:
|
||||
testdef = TestDef.import_from_xml_tree(testdef.export_to_xml(), self.formdefui.formdef)
|
||||
testdef.store()
|
||||
|
||||
return redirect('../%s/' % self.formdefui.formdef.id)
|
||||
|
||||
def get_check_deletion_message(self):
|
||||
|
@ -1609,6 +1655,7 @@ class FormDefPage(Directory, TempfileDirectoryMixin):
|
|||
return r.getvalue()
|
||||
|
||||
def export(self):
|
||||
self.formdef._export_tests = True
|
||||
return misc.xml_response(
|
||||
self.formdef,
|
||||
filename='%s-%s.wcs' % (self.formdef_export_prefix, self.formdef.url_name),
|
||||
|
@ -1720,6 +1767,11 @@ class FormDefPage(Directory, TempfileDirectoryMixin):
|
|||
context['workflow_options'][field.label] = htmltext('%s') % field.get_view_value(
|
||||
variables_form_data.get(field.id)
|
||||
)
|
||||
page = None
|
||||
for field in self.formdef.fields:
|
||||
if field.key == 'page':
|
||||
page = field
|
||||
field.on_page = page
|
||||
context['workflow_roles'] = list(self.get_workflow_roles_elements())
|
||||
context['backoffice_submission_roles'] = self._get_roles_label('backoffice_submission_roles')
|
||||
if self.formdef.tracking_code_verify_fields:
|
||||
|
@ -1817,7 +1869,7 @@ class FormsDirectory(AccessControlled, Directory):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.applications_dir = ApplicationsDirectory(self.formdef_class.xml_root_node)
|
||||
self.applications_dir = ApplicationsDirectory(self.formdef_class)
|
||||
|
||||
def _q_traverse(self, path):
|
||||
get_response().breadcrumb.append(('%s/' % self.section, self.top_title))
|
||||
|
@ -1847,6 +1899,7 @@ class FormsDirectory(AccessControlled, Directory):
|
|||
'view': self,
|
||||
'has_roles': bool(get_publisher().role_class.count()),
|
||||
'applications': Application.select_for_object_type(self.formdef_class.xml_root_node),
|
||||
'elements_label': self.formdef_class.verbose_name_plural,
|
||||
'has_sidebar': True,
|
||||
}
|
||||
formdefs = self.formdef_class.select(order_by='name', ignore_errors=True, lightweight=True)
|
||||
|
@ -2021,23 +2074,25 @@ class FormsDirectory(AccessControlled, Directory):
|
|||
self.imported_formdef = formdef
|
||||
formdef.disabled = True
|
||||
formdef.store()
|
||||
formdef.finish_tests_xml_import()
|
||||
return redirect('%s/' % formdef.id)
|
||||
|
||||
|
||||
class WorkflowChangeJob(AfterJob):
|
||||
def __init__(self, formdef, new_workflow_id, status_mapping):
|
||||
def __init__(self, formdef, new_workflow_id, status_mapping, user_id):
|
||||
super().__init__(
|
||||
label=_('Updating data for new workflow'),
|
||||
formdef_class=formdef.__class__,
|
||||
formdef_id=formdef.id,
|
||||
new_workflow_id=new_workflow_id,
|
||||
status_mapping=status_mapping,
|
||||
user_id=user_id,
|
||||
)
|
||||
|
||||
def execute(self):
|
||||
formdef = self.kwargs['formdef_class'].get(self.kwargs['formdef_id'])
|
||||
workflow = Workflow.get(self.kwargs['new_workflow_id'])
|
||||
formdef.change_workflow(workflow, self.kwargs['status_mapping'])
|
||||
formdef.change_workflow(workflow, self.kwargs['status_mapping'], user_id=self.kwargs.get('user_id'))
|
||||
|
||||
def done_action_url(self):
|
||||
formdef = self.kwargs['formdef_class'].get(self.kwargs['formdef_id'])
|
||||
|
|
|
@ -14,16 +14,40 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from django.utils.text import Truncator
|
||||
from quixote import get_publisher, get_request, get_response, redirect
|
||||
from quixote.directory import Directory
|
||||
from quixote.directory import AccessControlled, Directory
|
||||
from quixote.html import TemplateIO, htmltext
|
||||
|
||||
from wcs.backoffice.pagination import pagination_links
|
||||
from wcs.qommon import N_, _, errors, misc, ngettext, template
|
||||
from wcs.sql_criterias import Equal, NotEqual, NotNull
|
||||
from wcs.qommon.form import CheckboxesWidget, DateWidget, Form
|
||||
from wcs.sql_criterias import Equal, Less, NotEqual, NotNull, Null, Or
|
||||
|
||||
|
||||
class ErrorFrame:
|
||||
def __init__(self, context):
|
||||
self.context = context or {}
|
||||
|
||||
def source(self):
|
||||
if self.context.get('source_url'):
|
||||
return {
|
||||
'url': self.context.get('source_url'),
|
||||
'label': self.context.get('source_label'),
|
||||
}
|
||||
return None
|
||||
|
||||
def get_frame_lines(self):
|
||||
for key, value in self.context.items():
|
||||
key_label = {
|
||||
'condition': _('Condition'),
|
||||
'condition_type': _('Condition type'),
|
||||
}.get(key)
|
||||
if key_label:
|
||||
yield {'label': key_label, 'value': value}
|
||||
|
||||
|
||||
class LoggedErrorDirectory(Directory):
|
||||
|
@ -61,6 +85,10 @@ class LoggedErrorDirectory(Directory):
|
|||
'text': _('Text'),
|
||||
}.get(self.error.expression_type, _('Unknown'))
|
||||
|
||||
def get_context_frames(self):
|
||||
for frame_context in reversed(self.error.context.get('stack') or []):
|
||||
yield ErrorFrame(frame_context)
|
||||
|
||||
def get_tabs(self):
|
||||
r = TemplateIO(html=True)
|
||||
parts = (
|
||||
|
@ -132,8 +160,8 @@ class LoggedErrorDirectory(Directory):
|
|||
return redirect('..')
|
||||
|
||||
|
||||
class LoggedErrorsDirectory(Directory):
|
||||
_q_exports = ['']
|
||||
class LoggedErrorsDirectory(AccessControlled, Directory):
|
||||
_q_exports = ['', 'cleanup']
|
||||
|
||||
@classmethod
|
||||
def get_errors(cls, offset, limit, formdef_class=None, formdef_id=None, workflow_id=None):
|
||||
|
@ -208,7 +236,7 @@ class LoggedErrorsDirectory(Directory):
|
|||
self.formdef_id = formdef_id
|
||||
self.workflow_id = workflow_id
|
||||
|
||||
def _q_index(self):
|
||||
def _q_access(self):
|
||||
backoffice_root = get_publisher().get_backoffice_root()
|
||||
if not (
|
||||
backoffice_root.is_accessible('forms')
|
||||
|
@ -217,6 +245,7 @@ class LoggedErrorsDirectory(Directory):
|
|||
):
|
||||
raise errors.AccessForbiddenError()
|
||||
|
||||
def _q_index(self):
|
||||
get_response().breadcrumb.append(('logged-errors/', _('Logged Errors')))
|
||||
get_response().set_title(_('Logged Errors'))
|
||||
limit = misc.get_int_or_400(
|
||||
|
@ -240,6 +269,64 @@ class LoggedErrorsDirectory(Directory):
|
|||
},
|
||||
)
|
||||
|
||||
def cleanup(self):
|
||||
backoffice_root = get_publisher().get_backoffice_root()
|
||||
form = Form(enctype='multipart/form-data')
|
||||
options = []
|
||||
if backoffice_root.is_accessible('forms'):
|
||||
options.append(('formdef', _('Forms'), 'formdef'))
|
||||
if backoffice_root.is_accessible('cards'):
|
||||
options.append(('carddef', _('Card Models'), 'carddef'))
|
||||
if backoffice_root.is_accessible('workflows'):
|
||||
options.append(('others', _('Others'), 'others'))
|
||||
if not (self.formdef_id or self.workflow_id):
|
||||
form.add(
|
||||
CheckboxesWidget,
|
||||
'types',
|
||||
title=_('Error types'),
|
||||
value=[x[0] for x in options], # check all by default
|
||||
options=options,
|
||||
required=True,
|
||||
)
|
||||
form.add(
|
||||
DateWidget,
|
||||
'latest_occurence',
|
||||
title=_('Latest occurence'),
|
||||
value=datetime.date.today() - datetime.timedelta(days=180),
|
||||
required=True,
|
||||
)
|
||||
form.add_submit('submit', _('Submit'))
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('.')
|
||||
|
||||
if form.get_submit() == 'submit' and not form.has_errors():
|
||||
criterias = []
|
||||
|
||||
if self.formdef_id and self.formdef_class:
|
||||
criterias.append(Equal('formdef_id', self.formdef_id))
|
||||
criterias.append(Equal('formdef_class', self.formdef_class.__name__))
|
||||
elif self.workflow_id:
|
||||
criterias.append(Equal('workflow_id', self.workflow_id))
|
||||
else:
|
||||
if 'formdef' in form.get_widget('types').parse():
|
||||
criterias.append(Equal('formdef_class', 'FormDef'))
|
||||
if 'carddef' in form.get_widget('types').parse():
|
||||
criterias.append(Equal('formdef_class', 'CardDef'))
|
||||
if 'others' in form.get_widget('types').parse():
|
||||
criterias.append(Null('formdef_class'))
|
||||
criterias = [Or(criterias)]
|
||||
criterias.append(Less('latest_occurence_timestamp', form.get_widget('latest_occurence').parse()))
|
||||
get_publisher().loggederror_class.wipe(clause=criterias)
|
||||
return redirect('.')
|
||||
|
||||
get_response().set_title(_('Cleanup'))
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<h2>%s</h2>') % _('Cleanup')
|
||||
r += form.render()
|
||||
return r.getvalue()
|
||||
|
||||
def _q_lookup(self, component):
|
||||
try:
|
||||
error = get_publisher().loggederror_class.get(component)
|
||||
|
|
|
@ -46,7 +46,7 @@ class MailTemplatesDirectory(Directory):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.applications_dir = ApplicationsDirectory(MailTemplate.xml_root_node)
|
||||
self.applications_dir = ApplicationsDirectory(MailTemplate)
|
||||
|
||||
def _q_traverse(self, path):
|
||||
if not get_publisher().get_backoffice_root().is_global_accessible('workflows'):
|
||||
|
@ -67,6 +67,7 @@ class MailTemplatesDirectory(Directory):
|
|||
context = {
|
||||
'view': self,
|
||||
'applications': Application.select_for_object_type(MailTemplate.xml_root_node),
|
||||
'elements_label': MailTemplate.verbose_name_plural,
|
||||
'has_sidebar': True,
|
||||
}
|
||||
context.update(self.get_list_context(mail_templates))
|
||||
|
|
|
@ -36,10 +36,10 @@ from quixote.html import TemplateIO, htmltext
|
|||
from wcs.api_access import ApiAccess
|
||||
from wcs.blocks import BlockDef, BlockdefImportError
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.data_sources import NamedDataSource
|
||||
from wcs.data_sources import NamedDataSource, NamedDataSourceImportError
|
||||
from wcs.fields.map import MapOptionsMixin
|
||||
from wcs.formdef import FormDef, FormdefImportError, get_formdefs_of_all_kinds
|
||||
from wcs.qommon import _, audit, errors, get_cfg, ident, misc, template
|
||||
from wcs.qommon import _, audit, errors, get_cfg, ident, misc, pgettext_lazy, template
|
||||
from wcs.qommon.admin.cfg import cfg_submit, hobo_kwargs
|
||||
from wcs.qommon.admin.emails import EmailsDirectory
|
||||
from wcs.qommon.admin.texts import TextsDirectory
|
||||
|
@ -61,6 +61,7 @@ from wcs.qommon.form import (
|
|||
TextWidget,
|
||||
)
|
||||
from wcs.workflows import Workflow, WorkflowImportError
|
||||
from wcs.wscalls import NamedWsCallImportError
|
||||
|
||||
from .api_access import ApiAccessDirectory
|
||||
from .data_sources import NamedDataSourcesDirectory
|
||||
|
@ -487,7 +488,7 @@ class SettingsDirectory(AccessControlled, Directory):
|
|||
('data-sources', 'data_sources'),
|
||||
'wscalls',
|
||||
('api-access', 'api_access'),
|
||||
('submission-channels', 'submission_channels'),
|
||||
('backoffice-submission', 'backoffice_submission'),
|
||||
]
|
||||
|
||||
emails = EmailsDirectory()
|
||||
|
@ -636,10 +637,10 @@ class SettingsDirectory(AccessControlled, Directory):
|
|||
_('Geolocation'),
|
||||
_('Configure geolocation and geocoding'),
|
||||
)
|
||||
if enabled('submission-channels'):
|
||||
r += htmltext('<dt><a href="submission-channels">%s</a></dt> <dd>%s</dd>') % (
|
||||
_('Submission channels'),
|
||||
_('Configure submission channels related options'),
|
||||
if enabled('backoffice-submission'):
|
||||
r += htmltext('<dt><a href="backoffice-submission">%s</a></dt> <dd>%s</dd>') % (
|
||||
_('Backoffice Submission'),
|
||||
_('Configure backoffice submission related options'),
|
||||
)
|
||||
if enabled('users'):
|
||||
r += htmltext('<dt><a href="users/">%s</a></dt> <dd>%s</dd>') % (_('Users'), _('Configure users'))
|
||||
|
@ -1272,9 +1273,29 @@ $('#form_default-zoom-level').on('change', function() {
|
|||
)
|
||||
return redirect('.')
|
||||
|
||||
def submission_channels(self):
|
||||
def backoffice_submission(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
submission_channels_cfg = get_cfg('submission-channels', {})
|
||||
backoffice_submission_cfg = get_cfg('backoffice-submission', {})
|
||||
form.add(
|
||||
RadiobuttonsWidget,
|
||||
'sidebar_menu_entry',
|
||||
title=_('Sidebar menu entry'),
|
||||
value=backoffice_submission_cfg.get('sidebar_menu_entry', 'visible'),
|
||||
options=[
|
||||
('visible', pgettext_lazy('sidebar_menu_entry', 'Visible'), 'visible'),
|
||||
('hidden', pgettext_lazy('sidebar_menu_entry', 'Hidden'), 'hidden'),
|
||||
],
|
||||
extra_css_class='widget-inline-radio',
|
||||
)
|
||||
form.add(
|
||||
StringWidget,
|
||||
'redirect',
|
||||
title=_('URL for backoffice submission'),
|
||||
hint=_('Leave empty to use native screen.'),
|
||||
value=backoffice_submission_cfg.get('redirect', ''),
|
||||
size=80,
|
||||
)
|
||||
form.add(
|
||||
CheckboxWidget,
|
||||
'include-in-global-listing',
|
||||
|
@ -1288,18 +1309,15 @@ $('#form_default-zoom-level').on('change', function() {
|
|||
return redirect('.')
|
||||
|
||||
if not form.is_submitted() or form.has_errors():
|
||||
get_response().breadcrumb.append(('submission-channels', _('Submission channels')))
|
||||
get_response().set_title(_('Submission channels'))
|
||||
get_response().breadcrumb.append(('backoffice-submission', _('Backoffice Submission')))
|
||||
get_response().set_title(_('Backoffice submission settings'))
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<h2>%s</h2>') % _('Submission channels')
|
||||
r += htmltext('<h2>%s</h2>') % _('Backoffice submission settings')
|
||||
r += form.render()
|
||||
return r.getvalue()
|
||||
else:
|
||||
cfg_submit(
|
||||
form,
|
||||
'submission-channels',
|
||||
('include-in-global-listing',),
|
||||
)
|
||||
cfg_submit(form, 'submission-channels', ('include-in-global-listing',))
|
||||
cfg_submit(form, 'backoffice-submission', ('sidebar_menu_entry', 'redirect'))
|
||||
return redirect('.')
|
||||
|
||||
|
||||
|
@ -1495,7 +1513,10 @@ class SiteImportAfterJob(AfterJob):
|
|||
msg = _(e.msg) % e.msg_args
|
||||
if e.details:
|
||||
msg += ' [%s]' % e.details
|
||||
error = _('Failed to import a workflow (%s); site import did not complete.') % msg
|
||||
error = _('Failed to import objects (%s); site import did not complete.') % msg
|
||||
except (NamedDataSourceImportError, NamedWsCallImportError) as e:
|
||||
results = None
|
||||
error = _('Failed to import objects (%s); site import did not complete.') % str(e)
|
||||
|
||||
self.results = results
|
||||
if error:
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue